|
|
@@ -1,82 +1,172 @@
|
|
|
<template>
|
|
|
<view class="page">
|
|
|
- <!-- 顶部导航栏 -->
|
|
|
- <view class="nav-bar">
|
|
|
- <view class="nav-left" @tap="goBack">
|
|
|
- <text class="nav-icon">‹</text>
|
|
|
- <text class="nav-title">{{ todayText }}</text>
|
|
|
+ <view class="content">
|
|
|
+
|
|
|
+ <!-- ===== 夜间主题头部:日期 / 本次时长 / 入睡-醒来时间轴 ===== -->
|
|
|
+ <view class="hero">
|
|
|
+ <view class="hero-glow"></view>
|
|
|
+ <view class="hero-stars">
|
|
|
+ <view class="star" v-for="s in stars" :key="s.id"
|
|
|
+ :style="{ left: s.left, top: s.top, width: s.size, height: s.size, opacity: s.opacity, animationDelay: s.delay }"></view>
|
|
|
+ </view>
|
|
|
+ <view class="hero-moon">🌙</view>
|
|
|
+
|
|
|
+ <view class="hero-top">
|
|
|
+ <view class="hero-chip">
|
|
|
+ <text class="hero-chip-dot"></text>
|
|
|
+ <text class="hero-chip-text">今日睡眠打卡</text>
|
|
|
+ </view>
|
|
|
+ <text class="hero-date">{{ todayDate }} {{ todayWeek }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="hero-duration">
|
|
|
+ <text class="hero-duration-num">{{ durationText }}</text>
|
|
|
+ <text class="hero-duration-label">本次睡眠时长</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="hero-track">
|
|
|
+ <view class="hero-track-bar">
|
|
|
+ <view class="hero-track-fill" :style="{ width: durationPercent + '%' }"></view>
|
|
|
+ <view class="hero-track-mark"></view>
|
|
|
+ </view>
|
|
|
+ <view class="hero-ends">
|
|
|
+ <view class="hero-end">
|
|
|
+ <text class="hero-end-icon">🌙</text>
|
|
|
+ <text class="hero-end-time">{{ form.sleepTime || '--:--' }}</text>
|
|
|
+ <text class="hero-end-label">入睡</text>
|
|
|
+ </view>
|
|
|
+ <view class="hero-end hero-end-right">
|
|
|
+ <text class="hero-end-icon">☀️</text>
|
|
|
+ <text class="hero-end-time">{{ form.wakeTime || '--:--' }}</text>
|
|
|
+ <text class="hero-end-label">醒来</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="hero-tip">💡 建议青少年每天睡足 8-10 小时</view>
|
|
|
</view>
|
|
|
- </view>
|
|
|
|
|
|
- <scroll-view class="content" scroll-y>
|
|
|
- <!-- 睡眠时间 -->
|
|
|
+ <!-- ===== 睡眠时间 ===== -->
|
|
|
<view class="card">
|
|
|
- <view class="card-header">
|
|
|
- <text class="card-icon">🌙</text>
|
|
|
+ <view class="card-head">
|
|
|
+ <view class="card-head-icon"><text>🛏️</text></view>
|
|
|
<text class="card-title">睡眠时间</text>
|
|
|
+ <text class="card-hint">点击卡片选择</text>
|
|
|
</view>
|
|
|
<view class="card-body">
|
|
|
- <view class="form-group">
|
|
|
- <text class="form-label">入睡时间 *</text>
|
|
|
- <picker mode="time" :value="form.sleepTime" @change="function(e){form.sleepTime=e.detail.value}">
|
|
|
- <view class="picker-display">{{ form.sleepTime || '选择入睡时间' }}</view>
|
|
|
+ <view class="time-row">
|
|
|
+ <picker mode="time" :value="form.sleepTime" @change="onSleepTimeChange">
|
|
|
+ <view class="time-tile">
|
|
|
+ <text class="time-tile-icon">🌙</text>
|
|
|
+ <text class="time-tile-label">入睡时间</text>
|
|
|
+ <text class="time-tile-value">{{ form.sleepTime || '请选择' }}</text>
|
|
|
+ </view>
|
|
|
</picker>
|
|
|
- </view>
|
|
|
- <view class="form-group">
|
|
|
- <text class="form-label">醒来时间 *</text>
|
|
|
- <picker mode="time" :value="form.wakeTime" @change="function(e){form.wakeTime=e.detail.value}">
|
|
|
- <view class="picker-display">{{ form.wakeTime || '选择醒来时间' }}</view>
|
|
|
+ <view class="time-arrow"><text>→</text></view>
|
|
|
+ <picker mode="time" :value="form.wakeTime" @change="onWakeTimeChange">
|
|
|
+ <view class="time-tile time-tile-sun">
|
|
|
+ <text class="time-tile-icon">☀️</text>
|
|
|
+ <text class="time-tile-label">醒来时间</text>
|
|
|
+ <text class="time-tile-value">{{ form.wakeTime || '请选择' }}</text>
|
|
|
+ </view>
|
|
|
</picker>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 睡眠质量 & 备注 -->
|
|
|
+ <!-- ===== 睡眠质量(点选打分,替代滑动条) ===== -->
|
|
|
<view class="card">
|
|
|
- <view class="card-header">
|
|
|
- <text class="card-icon">⭐</text>
|
|
|
+ <view class="card-head">
|
|
|
+ <view class="card-head-icon"><text>⭐</text></view>
|
|
|
<text class="card-title">睡眠质量</text>
|
|
|
+ <text class="card-hint">1-10 分</text>
|
|
|
</view>
|
|
|
<view class="card-body">
|
|
|
- <view class="form-group">
|
|
|
- <text class="form-label">睡眠质量(1-10)</text>
|
|
|
- <view class="slider-row">
|
|
|
- <text class="slider-min">差</text>
|
|
|
- <slider min="1" max="10" :value="form.qualityScore" @change="function(e){form.qualityScore=e.detail.value}" block-size="20" activeColor="#FF8C42" />
|
|
|
- <text class="slider-val">{{ form.qualityScore }}</text>
|
|
|
- <text class="slider-max">好</text>
|
|
|
+ <view class="quality-head">
|
|
|
+ <view class="quality-face">{{ qualityFace }}</view>
|
|
|
+ <view class="quality-info">
|
|
|
+ <view class="quality-score-row">
|
|
|
+ <text class="quality-score">{{ form.qualityScore }}</text>
|
|
|
+ <text class="quality-unit">分</text>
|
|
|
+ <text class="quality-badge" :class="'quality-badge quality-badge-' + qualityTier">{{ qualityLabel }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="quality-desc">{{ qualityDesc }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="q-chips">
|
|
|
+ <view class="q-chip" v-for="o in qualityOptions" :key="o.v"
|
|
|
+ :class="'q-chip q-chip-' + o.tier + (form.qualityScore === o.v ? ' q-chip-active' : '')"
|
|
|
+ @tap="setQuality(o.v)">
|
|
|
+ <text>{{ o.v }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
- <view class="form-group">
|
|
|
- <text class="form-label">备注(选填)</text>
|
|
|
- <input class="form-input" v-model="form.remark" placeholder="睡眠感受..." />
|
|
|
+
|
|
|
+ <view class="q-scale">
|
|
|
+ <text class="q-scale-end">1 · 很差</text>
|
|
|
+ <text class="q-scale-mid">点击数字打分</text>
|
|
|
+ <text class="q-scale-end">10 · 很好</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- ===== 快速标签(选填,将写入备注) ===== -->
|
|
|
+ <view class="card">
|
|
|
+ <view class="card-head">
|
|
|
+ <view class="card-head-icon"><text>🏷️</text></view>
|
|
|
+ <text class="card-title">睡眠情况</text>
|
|
|
+ <text class="card-hint">选填 · 可多选</text>
|
|
|
+ </view>
|
|
|
+ <view class="card-body">
|
|
|
+ <view class="tag-cloud">
|
|
|
+ <view class="s-tag" v-for="t in quickTags" :key="t"
|
|
|
+ :class="selectedTags.indexOf(t) > -1 ? 's-tag s-tag-active' : 's-tag'"
|
|
|
+ @tap="toggleTag(t)">
|
|
|
+ <text>{{ t }}</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 提交按钮 -->
|
|
|
+ <!-- ===== 备注 ===== -->
|
|
|
+ <view class="card">
|
|
|
+ <view class="card-head">
|
|
|
+ <view class="card-head-icon"><text>📝</text></view>
|
|
|
+ <text class="card-title">备注</text>
|
|
|
+ <text class="card-hint">{{ form.remark.length }}/100</text>
|
|
|
+ </view>
|
|
|
+ <view class="card-body">
|
|
|
+ <textarea class="remark-input" v-model="form.remark"
|
|
|
+ placeholder="昨晚睡得怎么样?有什么想记录的…" placeholder-class="ph-gray" maxlength="100" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- ===== 提交按钮 ===== -->
|
|
|
<view class="submit-wrap">
|
|
|
- <view class="submit-btn" :class="{ 'submitting': submitting }" @tap="doCheckin">
|
|
|
- <text>{{ submitting ? '提交中...' : '✓ 完成打卡' }}</text>
|
|
|
+ <view class="submit-btn" :class="submitting ? 'submit-btn submitting' : 'submit-btn'" @tap="doCheckin">
|
|
|
+ <text class="submit-text">{{ submitting ? '打卡中...' : '完成打卡' }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 历史记录 -->
|
|
|
+ <!-- ===== 历史记录 ===== -->
|
|
|
<view class="card" v-if="history.length > 0">
|
|
|
- <view class="card-header">
|
|
|
- <text class="card-icon">📜</text>
|
|
|
+ <view class="card-head">
|
|
|
+ <view class="card-head-icon"><text>📜</text></view>
|
|
|
<text class="card-title">近期睡眠</text>
|
|
|
+ <text class="card-hint" v-if="avgDurationText">平均 {{ avgDurationText }} · {{ avgQuality }} 分</text>
|
|
|
</view>
|
|
|
<view class="card-body">
|
|
|
<view class="history-list">
|
|
|
<view class="history-item" v-for="item in history" :key="item.id">
|
|
|
- <view class="history-left">
|
|
|
- <text class="history-type">{{ item.sleepTime }} - {{ item.wakeTime }}</text>
|
|
|
- <text class="history-content">{{ item.durationMinutes }}分钟 · 质量{{ item.qualityScore }}分</text>
|
|
|
- </view>
|
|
|
- <view class="history-right">
|
|
|
- <text class="history-date">{{ formatDate(item.createdAt) }}</text>
|
|
|
+ <view class="history-badge"><text class="history-badge-icon">🌙</text></view>
|
|
|
+ <view class="history-main">
|
|
|
+ <text class="history-line1">{{ item._range }}</text>
|
|
|
+ <text class="history-line2">{{ item._duration }} · 质量 {{ item.qualityScore || '-' }} 分</text>
|
|
|
+ <view class="qbar">
|
|
|
+ <view class="qbar-fill" :style="{ width: item._percent + '%', background: item._barBg }"></view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
+ <text class="history-date">{{ formatDate(item.createdAt) }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -84,13 +174,13 @@
|
|
|
|
|
|
<!-- 底部留白 -->
|
|
|
<view class="bottom-spacer"></view>
|
|
|
- </scroll-view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import config from '../../config'
|
|
|
-import { parseDate } from '../../utils/format.js'
|
|
|
+import { formatDate } from '../../utils/format.js'
|
|
|
|
|
|
export default {
|
|
|
data() {
|
|
|
@@ -99,7 +189,41 @@ export default {
|
|
|
taskId: null,
|
|
|
submitting: false,
|
|
|
history: [],
|
|
|
- todayText: '',
|
|
|
+ todayDate: '',
|
|
|
+ todayWeek: '',
|
|
|
+ stars: [
|
|
|
+ { id: 's1', left: '6%', top: '16%', size: '8rpx', opacity: 0.9, delay: '0s' },
|
|
|
+ { id: 's2', left: '16%', top: '42%', size: '5rpx', opacity: 0.6, delay: '0.8s' },
|
|
|
+ { id: 's3', left: '26%', top: '12%', size: '6rpx', opacity: 0.75, delay: '1.6s' },
|
|
|
+ { id: 's4', left: '38%', top: '46%', size: '4rpx', opacity: 0.5, delay: '0.4s' },
|
|
|
+ { id: 's5', left: '48%', top: '18%', size: '5rpx', opacity: 0.65, delay: '2.2s' }
|
|
|
+ ],
|
|
|
+ quickTags: ['入睡困难', '夜醒', '多梦', '打鼾', '早醒', '赖床', '熬夜', '午睡过长'],
|
|
|
+ selectedTags: [],
|
|
|
+ qualityOptions: [
|
|
|
+ { v: 1, tier: 'poor' },
|
|
|
+ { v: 2, tier: 'poor' },
|
|
|
+ { v: 3, tier: 'poor' },
|
|
|
+ { v: 4, tier: 'fair' },
|
|
|
+ { v: 5, tier: 'fair' },
|
|
|
+ { v: 6, tier: 'good' },
|
|
|
+ { v: 7, tier: 'good' },
|
|
|
+ { v: 8, tier: 'great' },
|
|
|
+ { v: 9, tier: 'great' },
|
|
|
+ { v: 10, tier: 'great' }
|
|
|
+ ],
|
|
|
+ qualityMap: {
|
|
|
+ 1: { face: '😣', label: '很差', tier: 'poor', desc: '几乎整晚没睡好,白天完全不在状态' },
|
|
|
+ 2: { face: '😣', label: '很差', tier: 'poor', desc: '睡得很浅,反复醒,醒来很累' },
|
|
|
+ 3: { face: '😕', label: '较差', tier: 'poor', desc: '睡得不太踏实,精神差一些' },
|
|
|
+ 4: { face: '😕', label: '偏差', tier: 'fair', desc: '将就睡了一晚,不算安稳' },
|
|
|
+ 5: { face: '😐', label: '一般', tier: 'fair', desc: '不好不差,勉强够用' },
|
|
|
+ 6: { face: '🙂', label: '尚可', tier: 'good', desc: '睡得还行,基本恢复精力' },
|
|
|
+ 7: { face: '🙂', label: '不错', tier: 'good', desc: '睡得比较踏实,状态正常' },
|
|
|
+ 8: { face: '😊', label: '良好', tier: 'great', desc: '睡得很沉,醒来神清气爽' },
|
|
|
+ 9: { face: '😄', label: '很好', tier: 'great', desc: '一觉到天亮,几乎不想起床' },
|
|
|
+ 10: { face: '🤩', label: '极好', tier: 'great', desc: '完美的一觉,元气满满' }
|
|
|
+ },
|
|
|
form: {
|
|
|
sleepTime: '21:00',
|
|
|
wakeTime: '07:00',
|
|
|
@@ -108,9 +232,70 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
- onLoad: function(options) {
|
|
|
+ computed: {
|
|
|
+ // 本次睡眠时长(分钟),跨午夜自动换算
|
|
|
+ durationMinutes: function () {
|
|
|
+ var s = this.form.sleepTime
|
|
|
+ var w = this.form.wakeTime
|
|
|
+ if (!s || !w) return 0
|
|
|
+ var sp = String(s).split(':')
|
|
|
+ var wp = String(w).split(':')
|
|
|
+ var sm = parseInt(sp[0]) * 60 + parseInt(sp[1])
|
|
|
+ var wm = parseInt(wp[0]) * 60 + parseInt(wp[1])
|
|
|
+ if (isNaN(sm) || isNaN(wm)) return 0
|
|
|
+ return wm > sm ? wm - sm : (24 * 60 - sm + wm)
|
|
|
+ },
|
|
|
+ durationText: function () {
|
|
|
+ return this.durationFromMinutes(this.durationMinutes)
|
|
|
+ },
|
|
|
+ // 相对 10 小时的睡眠进度百分比
|
|
|
+ durationPercent: function () {
|
|
|
+ if (!this.durationMinutes) return 0
|
|
|
+ return Math.min(100, Math.round(this.durationMinutes / 600 * 100))
|
|
|
+ },
|
|
|
+ qualityFace: function () {
|
|
|
+ var m = this.qualityMap[this.form.qualityScore]
|
|
|
+ return m ? m.face : '🙂'
|
|
|
+ },
|
|
|
+ qualityLabel: function () {
|
|
|
+ var m = this.qualityMap[this.form.qualityScore]
|
|
|
+ return m ? m.label : '一般'
|
|
|
+ },
|
|
|
+ qualityTier: function () {
|
|
|
+ var m = this.qualityMap[this.form.qualityScore]
|
|
|
+ return m ? m.tier : 'fair'
|
|
|
+ },
|
|
|
+ qualityDesc: function () {
|
|
|
+ var m = this.qualityMap[this.form.qualityScore]
|
|
|
+ return m ? m.desc : ''
|
|
|
+ },
|
|
|
+ avgDurationText: function () {
|
|
|
+ var total = 0
|
|
|
+ var count = 0
|
|
|
+ this.history.forEach(function (h) {
|
|
|
+ if (h.durationMinutes) { total += h.durationMinutes; count++ }
|
|
|
+ })
|
|
|
+ if (!count) return ''
|
|
|
+ return this.durationFromMinutes(Math.round(total / count))
|
|
|
+ },
|
|
|
+ avgQuality: function () {
|
|
|
+ var total = 0
|
|
|
+ var count = 0
|
|
|
+ this.history.forEach(function (h) {
|
|
|
+ if (h.qualityScore) { total += h.qualityScore; count++ }
|
|
|
+ })
|
|
|
+ if (!count) return 0
|
|
|
+ return (total / count).toFixed(1)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad: function (options) {
|
|
|
this.taskId = options.taskId || null
|
|
|
- this.todayText = this.getToday()
|
|
|
+ var d = new Date()
|
|
|
+ var weekdays = ['日', '一', '二', '三', '四', '五', '六']
|
|
|
+ var m2 = ('0' + (d.getMonth() + 1)).slice(-2)
|
|
|
+ var day = ('0' + d.getDate()).slice(-2)
|
|
|
+ this.todayDate = d.getFullYear() + '-' + m2 + '-' + day
|
|
|
+ this.todayWeek = '周' + weekdays[d.getDay()]
|
|
|
var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
|
|
|
if (role !== 'child') {
|
|
|
this.memberId = uni.getStorageSync('currentChildId') || uni.getStorageSync('currentMemberId') || uni.getStorageSync('userId') || null
|
|
|
@@ -118,21 +303,31 @@ export default {
|
|
|
this.loadHistory()
|
|
|
},
|
|
|
methods: {
|
|
|
- getToday: function() {
|
|
|
- var d = new Date()
|
|
|
- var weekdays = ['日', '一', '二', '三', '四', '五', '六']
|
|
|
- return d.getFullYear() + '年' + (d.getMonth() + 1) + '月' + d.getDate() + '日 周' + weekdays[d.getDay()]
|
|
|
- },
|
|
|
- goBack: function() { uni.navigateBack() },
|
|
|
- formatDate: function(dateStr) {
|
|
|
- if (!dateStr) return ''
|
|
|
- var d = parseDate(dateStr)
|
|
|
- if (!d) return dateStr
|
|
|
- var m2 = (d.getMonth() + 1).toString().padStart(2, '0')
|
|
|
- var day = d.getDate().toString().padStart(2, '0')
|
|
|
- return m2 + '-' + day
|
|
|
- },
|
|
|
- loadHistory: function() {
|
|
|
+ onSleepTimeChange: function (e) {
|
|
|
+ this.form.sleepTime = e.detail.value
|
|
|
+ },
|
|
|
+ onWakeTimeChange: function (e) {
|
|
|
+ this.form.wakeTime = e.detail.value
|
|
|
+ },
|
|
|
+ setQuality: function (score) {
|
|
|
+ this.form.qualityScore = score
|
|
|
+ },
|
|
|
+ toggleTag: function (tag) {
|
|
|
+ var idx = this.selectedTags.indexOf(tag)
|
|
|
+ if (idx > -1) {
|
|
|
+ this.selectedTags.splice(idx, 1)
|
|
|
+ } else {
|
|
|
+ this.selectedTags.push(tag)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ durationFromMinutes: function (mins) {
|
|
|
+ if (!mins || mins <= 0) return '--'
|
|
|
+ var h = Math.floor(mins / 60)
|
|
|
+ var m = mins % 60
|
|
|
+ if (h > 0) return h + ' 小时' + (m > 0 ? ' ' + m + ' 分' : '')
|
|
|
+ return m + ' 分钟'
|
|
|
+ },
|
|
|
+ loadHistory: function () {
|
|
|
var self = this
|
|
|
var token = uni.getStorageSync('token')
|
|
|
if (!token || !self.memberId) return
|
|
|
@@ -141,60 +336,83 @@ export default {
|
|
|
method: 'POST',
|
|
|
data: { memberId: self.memberId },
|
|
|
header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
|
|
- success: function(res) {
|
|
|
+ success: function (res) {
|
|
|
if (res.data && res.data.code === 200 && res.data.data) {
|
|
|
- self.history = res.data.data.slice(0, 5)
|
|
|
+ var arr = Array.isArray(res.data.data) ? res.data.data : []
|
|
|
+ self.history = arr.slice(0, 5).map(function (item) {
|
|
|
+ self.decorateHistoryItem(item)
|
|
|
+ return item
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
- async doCheckin() {
|
|
|
+ // 预计算列表渲染字段(避免模板中调用带参方法)
|
|
|
+ decorateHistoryItem: function (item) {
|
|
|
+ var score = item.qualityScore || 0
|
|
|
+ item._range = (item.sleepTime ? String(item.sleepTime).slice(0, 5) : '--:--') +
|
|
|
+ ' - ' + (item.wakeTime ? String(item.wakeTime).slice(0, 5) : '--:--')
|
|
|
+ item._duration = this.durationFromMinutes(item.durationMinutes)
|
|
|
+ item._percent = Math.min(100, Math.max(0, Math.round(score * 10)))
|
|
|
+ if (score >= 8) item._barBg = 'linear-gradient(90deg,#818CF8,#6366F1)'
|
|
|
+ else if (score >= 6) item._barBg = 'linear-gradient(90deg,#34D399,#10B981)'
|
|
|
+ else if (score >= 4) item._barBg = 'linear-gradient(90deg,#FBBF24,#F59E0B)'
|
|
|
+ else item._barBg = 'linear-gradient(90deg,#FB7185,#EF4444)'
|
|
|
+ },
|
|
|
+ buildRemark: function () {
|
|
|
+ var remark = this.form.remark ? this.form.remark.trim() : ''
|
|
|
+ var tagText = this.selectedTags.join(',')
|
|
|
+ if (tagText && remark) return tagText + ',' + remark
|
|
|
+ if (tagText) return tagText
|
|
|
+ return remark
|
|
|
+ },
|
|
|
+ doCheckin: function () {
|
|
|
var self = this
|
|
|
+ if (self.submitting) return
|
|
|
if (!self.form.sleepTime) { uni.showToast({ title: '请选择入睡时间', icon: 'none' }); return }
|
|
|
if (!self.form.wakeTime) { uni.showToast({ title: '请选择醒来时间', icon: 'none' }); return }
|
|
|
if (self.form.sleepTime === self.form.wakeTime) { uni.showToast({ title: '入睡和醒来时间不能相同', icon: 'none' }); return }
|
|
|
+ if (!self.form.qualityScore) { uni.showToast({ title: '请选择睡眠质量', icon: 'none' }); return }
|
|
|
if (!self.memberId) { uni.showToast({ title: '成员ID无效', icon: 'none' }); return }
|
|
|
self.submitting = true
|
|
|
- try {
|
|
|
- var sleepParts = self.form.sleepTime.split(':')
|
|
|
- var wakeParts = self.form.wakeTime.split(':')
|
|
|
- var sleepMin = parseInt(sleepParts[0]) * 60 + parseInt(sleepParts[1])
|
|
|
- var wakeMin = parseInt(wakeParts[0]) * 60 + parseInt(wakeParts[1])
|
|
|
- var duration = wakeMin > sleepMin ? wakeMin - sleepMin : (24 * 60 - sleepMin + wakeMin)
|
|
|
- var token = uni.getStorageSync('token')
|
|
|
- uni.request({
|
|
|
- url: config.API_BASE_URL + '/api/health/sleep/create',
|
|
|
- method: 'POST',
|
|
|
- data: {
|
|
|
- memberId: self.memberId,
|
|
|
- taskId: self.taskId || undefined,
|
|
|
- sleepTime: self.form.sleepTime,
|
|
|
- wakeTime: self.form.wakeTime,
|
|
|
- durationMinutes: duration,
|
|
|
- qualityScore: self.form.qualityScore,
|
|
|
- remark: self.form.remark || ''
|
|
|
- },
|
|
|
- header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
|
|
- success: function(res) {
|
|
|
- self.submitting = false
|
|
|
- if (res.data && res.data.code === 200) {
|
|
|
- uni.showToast({ title: self.taskId ? '打卡成功,任务已完成' : '打卡成功', icon: 'success' })
|
|
|
- setTimeout(function() {
|
|
|
- uni.navigateBack()
|
|
|
- }, 800)
|
|
|
- } else {
|
|
|
- uni.showToast({ title: (res.data && res.data.message) || '打卡失败', icon: 'none' })
|
|
|
- }
|
|
|
- },
|
|
|
- fail: function() {
|
|
|
- self.submitting = false
|
|
|
- uni.showToast({ title: '网络错误', icon: 'none' })
|
|
|
+ var token = uni.getStorageSync('token')
|
|
|
+ uni.request({
|
|
|
+ url: config.API_BASE_URL + '/api/health/sleep/create',
|
|
|
+ method: 'POST',
|
|
|
+ data: {
|
|
|
+ memberId: self.memberId,
|
|
|
+ taskId: self.taskId || undefined,
|
|
|
+ sleepTime: self.form.sleepTime,
|
|
|
+ wakeTime: self.form.wakeTime,
|
|
|
+ durationMinutes: self.durationMinutes,
|
|
|
+ qualityScore: self.form.qualityScore,
|
|
|
+ remark: self.buildRemark()
|
|
|
+ },
|
|
|
+ header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
|
|
+ success: function (res) {
|
|
|
+ self.submitting = false
|
|
|
+ if (res.data && res.data.code === 200) {
|
|
|
+ uni.showToast({ title: self.taskId ? '打卡成功,任务已完成' : '打卡成功', icon: 'success' })
|
|
|
+ // 本地插入新记录(复用已返回的数据,不重复请求列表接口)
|
|
|
+ var created = res.data.data
|
|
|
+ if (created) {
|
|
|
+ self.decorateHistoryItem(created)
|
|
|
+ var list = self.history.slice()
|
|
|
+ list.unshift(created)
|
|
|
+ self.history = list.slice(0, 5)
|
|
|
+ }
|
|
|
+ setTimeout(function () {
|
|
|
+ uni.navigateBack()
|
|
|
+ }, 800)
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: (res.data && res.data.message) || '打卡失败', icon: 'none' })
|
|
|
}
|
|
|
- })
|
|
|
- } catch (e) {
|
|
|
- self.submitting = false
|
|
|
- uni.showToast({ title: '提交失败', icon: 'none' })
|
|
|
- }
|
|
|
+ },
|
|
|
+ fail: function () {
|
|
|
+ self.submitting = false
|
|
|
+ uni.showToast({ title: '网络错误', icon: 'none' })
|
|
|
+ }
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -202,45 +420,360 @@ export default {
|
|
|
|
|
|
<style scoped>
|
|
|
/* ===== 页面基础 ===== */
|
|
|
-.page { min-height: 100vh; background: #FFF7ED; }
|
|
|
-.nav-bar { height: 88rpx; padding: 24rpx 24rpx 0; display: flex; align-items: flex-end; }
|
|
|
-.nav-left { display: flex; align-items: center; gap: 10rpx; }
|
|
|
-.nav-icon { font-size: 48rpx; color: #333; line-height: 1; }
|
|
|
-.nav-title { font-size: 34rpx; font-weight: 600; color: #333; }
|
|
|
-.content { padding-bottom: 40rpx; }
|
|
|
+.page { min-height: 100vh; background: #EEF1F8; }
|
|
|
+.content { padding: 20rpx 0 0; }
|
|
|
+
|
|
|
+/* ===== 夜间主题头部 ===== */
|
|
|
+.hero {
|
|
|
+ position: relative;
|
|
|
+ margin: 8rpx 24rpx 8rpx;
|
|
|
+ padding: 36rpx 32rpx 32rpx;
|
|
|
+ border-radius: 32rpx;
|
|
|
+ overflow: hidden;
|
|
|
+ background: linear-gradient(155deg, #3B4A8C 0%, #2A3470 45%, #1B2350 100%);
|
|
|
+ box-shadow: 0 16rpx 40rpx rgba(27, 35, 80, 0.35);
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+.hero-glow {
|
|
|
+ position: absolute;
|
|
|
+ top: -120rpx;
|
|
|
+ right: -80rpx;
|
|
|
+ width: 320rpx;
|
|
|
+ height: 320rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: radial-gradient(circle, rgba(255, 209, 102, 0.4) 0%, rgba(255, 209, 102, 0) 70%);
|
|
|
+}
|
|
|
+.hero-moon {
|
|
|
+ position: absolute;
|
|
|
+ top: 20rpx;
|
|
|
+ right: 32rpx;
|
|
|
+ font-size: 72rpx;
|
|
|
+ line-height: 1;
|
|
|
+ animation: moon-float 4s ease-in-out infinite;
|
|
|
+}
|
|
|
+@keyframes moon-float {
|
|
|
+ 0%, 100% { transform: translateY(0); }
|
|
|
+ 50% { transform: translateY(-10rpx); }
|
|
|
+}
|
|
|
+.star {
|
|
|
+ position: absolute;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #FFE9A8;
|
|
|
+ animation: star-twinkle 2.4s ease-in-out infinite;
|
|
|
+}
|
|
|
+@keyframes star-twinkle {
|
|
|
+ 0%, 100% { opacity: 0.2; transform: scale(0.8); }
|
|
|
+ 50% { opacity: 0.95; transform: scale(1.15); }
|
|
|
+}
|
|
|
+.hero-top {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+}
|
|
|
+.hero-chip {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10rpx;
|
|
|
+ padding: 8rpx 20rpx;
|
|
|
+ border-radius: 999rpx;
|
|
|
+ background: rgba(255, 255, 255, 0.14);
|
|
|
+}
|
|
|
+.hero-chip-dot {
|
|
|
+ width: 10rpx;
|
|
|
+ height: 10rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #FFD166;
|
|
|
+ box-shadow: 0 0 12rpx rgba(255, 209, 102, 0.9);
|
|
|
+}
|
|
|
+.hero-chip-text { font-size: 22rpx; color: #fff; }
|
|
|
+.hero-date { font-size: 24rpx; color: rgba(255, 255, 255, 0.75); }
|
|
|
+.hero-duration {
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+ margin-top: 24rpx;
|
|
|
+}
|
|
|
+.hero-duration-num {
|
|
|
+ font-size: 56rpx;
|
|
|
+ font-weight: 800;
|
|
|
+ color: #fff;
|
|
|
+ line-height: 1.1;
|
|
|
+}
|
|
|
+.hero-duration-label {
|
|
|
+ display: block;
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: rgba(255, 255, 255, 0.7);
|
|
|
+ margin-top: 6rpx;
|
|
|
+}
|
|
|
+.hero-track {
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+ margin-top: 28rpx;
|
|
|
+}
|
|
|
+.hero-track-bar {
|
|
|
+ position: relative;
|
|
|
+ height: 12rpx;
|
|
|
+ border-radius: 999rpx;
|
|
|
+ background: rgba(255, 255, 255, 0.16);
|
|
|
+}
|
|
|
+.hero-track-fill {
|
|
|
+ height: 100%;
|
|
|
+ border-radius: 999rpx;
|
|
|
+ background: linear-gradient(90deg, #FFD166, #FF9F68);
|
|
|
+ transition: width 0.3s ease;
|
|
|
+}
|
|
|
+.hero-track-mark {
|
|
|
+ position: absolute;
|
|
|
+ left: 80%;
|
|
|
+ top: -4rpx;
|
|
|
+ width: 4rpx;
|
|
|
+ height: 20rpx;
|
|
|
+ border-radius: 2rpx;
|
|
|
+ background: rgba(255, 255, 255, 0.85);
|
|
|
+}
|
|
|
+.hero-ends {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-top: 16rpx;
|
|
|
+}
|
|
|
+.hero-end {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 4rpx;
|
|
|
+}
|
|
|
+.hero-end-right { align-items: flex-end; }
|
|
|
+.hero-end-icon { font-size: 32rpx; }
|
|
|
+.hero-end-time { font-size: 34rpx; font-weight: 700; color: #fff; }
|
|
|
+.hero-end-label { font-size: 20rpx; color: rgba(255, 255, 255, 0.6); }
|
|
|
+.hero-tip {
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
+ margin-top: 24rpx;
|
|
|
+ padding-top: 20rpx;
|
|
|
+ border-top: 1rpx solid rgba(255, 255, 255, 0.12);
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: rgba(255, 255, 255, 0.65);
|
|
|
+}
|
|
|
|
|
|
/* ===== 卡片通用 ===== */
|
|
|
-.card { margin: 16rpx 20rpx; background: #fff; border-radius: 20rpx; overflow: hidden; box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.05); }
|
|
|
-.card-header { display: flex; align-items: center; gap: 10rpx; padding: 20rpx 24rpx 8rpx; border-bottom: 1rpx solid #f5f5f5; background: linear-gradient(90deg, #FFFAF0, #fff); }
|
|
|
-.card-icon { font-size: 32rpx; }
|
|
|
-.card-title { font-size: 30rpx; font-weight: 600; color: #333; }
|
|
|
-.card-body { padding: 20rpx 24rpx 24rpx; }
|
|
|
-
|
|
|
-/* ===== 表单 ===== */
|
|
|
-.form-group { margin-bottom: 20rpx; }
|
|
|
-.form-label { font-size: 26rpx; color: #333; font-weight: 500; margin-bottom: 10rpx; display: block; }
|
|
|
-.picker-display { border: 1rpx solid #e8e8e8; border-radius: 14rpx; padding: 18rpx; font-size: 26rpx; background: #FAFAFA; color: #333; min-height: 40rpx; }
|
|
|
-.slider-row { display: flex; align-items: center; gap: 10rpx; }
|
|
|
-.slider-min { font-size: 22rpx; color: #999; flex-shrink: 0; }
|
|
|
-.slider-val { font-size: 26rpx; color: #FF8C42; font-weight: 600; width: 36rpx; text-align: center; }
|
|
|
-.slider-max { font-size: 22rpx; color: #999; flex-shrink: 0; }
|
|
|
-.form-input { width: 100%; height: 76rpx; border: 1rpx solid #e8e8e8; border-radius: 14rpx; padding: 0 16rpx; font-size: 26rpx; background: #FAFAFA; box-sizing: border-box; }
|
|
|
+.card {
|
|
|
+ margin: 24rpx 24rpx 0;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 28rpx;
|
|
|
+ box-shadow: 0 8rpx 28rpx rgba(15, 23, 42, 0.05);
|
|
|
+}
|
|
|
+.card-head {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 14rpx;
|
|
|
+ padding: 26rpx 28rpx 14rpx;
|
|
|
+}
|
|
|
+.card-head-icon {
|
|
|
+ width: 56rpx;
|
|
|
+ height: 56rpx;
|
|
|
+ border-radius: 18rpx;
|
|
|
+ background: linear-gradient(135deg, #EEF2FF, #E0E7FF);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 30rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.card-title {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 30rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1E293B;
|
|
|
+}
|
|
|
+.card-hint { font-size: 22rpx; color: #94A3B8; }
|
|
|
+.card-body { padding: 4rpx 28rpx 30rpx; }
|
|
|
+
|
|
|
+/* ===== 睡眠时间 ===== */
|
|
|
+.time-row { display: flex; align-items: stretch; gap: 16rpx; }
|
|
|
+.time-tile {
|
|
|
+ flex: 1;
|
|
|
+ background: linear-gradient(160deg, #F8FAFF, #F1F5FF);
|
|
|
+ border: 2rpx solid #E6EBF5;
|
|
|
+ border-radius: 24rpx;
|
|
|
+ padding: 24rpx 16rpx;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8rpx;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+.time-tile:active { transform: scale(0.97); border-color: #C7D2FE; }
|
|
|
+.time-tile-icon { font-size: 40rpx; line-height: 1; }
|
|
|
+.time-tile-label { font-size: 22rpx; color: #94A3B8; }
|
|
|
+.time-tile-value { font-size: 40rpx; font-weight: 700; color: #1E293B; }
|
|
|
+.time-arrow {
|
|
|
+ align-self: center;
|
|
|
+ font-size: 32rpx;
|
|
|
+ color: #94A3B8;
|
|
|
+ font-weight: 700;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 睡眠质量 ===== */
|
|
|
+.quality-head {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 24rpx;
|
|
|
+ background: linear-gradient(160deg, #F8FAFF, #F1F5FF);
|
|
|
+ border-radius: 24rpx;
|
|
|
+ padding: 26rpx 24rpx;
|
|
|
+ margin-bottom: 26rpx;
|
|
|
+}
|
|
|
+.quality-face {
|
|
|
+ width: 116rpx;
|
|
|
+ height: 116rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 6rpx 18rpx rgba(79, 70, 229, 0.12);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 62rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.quality-info { flex: 1; min-width: 0; }
|
|
|
+.quality-score-row { display: flex; align-items: baseline; }
|
|
|
+.quality-score {
|
|
|
+ font-size: 54rpx;
|
|
|
+ font-weight: 800;
|
|
|
+ color: #1E293B;
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+.quality-unit { font-size: 24rpx; color: #94A3B8; margin-left: 6rpx; }
|
|
|
+.quality-badge {
|
|
|
+ margin-left: 14rpx;
|
|
|
+ padding: 6rpx 18rpx;
|
|
|
+ border-radius: 999rpx;
|
|
|
+ font-size: 22rpx;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+.quality-badge-poor { background: #FEE2E2; color: #DC2626; }
|
|
|
+.quality-badge-fair { background: #FEF3C7; color: #B45309; }
|
|
|
+.quality-badge-good { background: #D1FAE5; color: #047857; }
|
|
|
+.quality-badge-great { background: #E0E7FF; color: #4338CA; }
|
|
|
+.quality-desc { display: block; font-size: 24rpx; color: #64748B; margin-top: 12rpx; }
|
|
|
+
|
|
|
+.q-chips { display: flex; flex-wrap: wrap; gap: 14rpx; }
|
|
|
+.q-chip {
|
|
|
+ flex: 1;
|
|
|
+ height: 78rpx;
|
|
|
+ border-radius: 22rpx;
|
|
|
+ background: #F8FAFC;
|
|
|
+ border: 2rpx solid #EDF1F7;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ transition: all 0.18s ease;
|
|
|
+}
|
|
|
+.q-chip text { font-size: 30rpx; font-weight: 600; color: #64748B; }
|
|
|
+.q-chip:active { transform: scale(0.94); }
|
|
|
+.q-chip-active { border-color: transparent; transform: translateY(-4rpx); }
|
|
|
+.q-chip-active text { color: #fff; }
|
|
|
+.q-chip-poor text { color: #F87171; }
|
|
|
+.q-chip-fair text { color: #D97706; }
|
|
|
+.q-chip-good text { color: #059669; }
|
|
|
+.q-chip-great text { color: #4F46E5; }
|
|
|
+.q-chip-poor.q-chip-active { background: linear-gradient(135deg, #FB7185, #EF4444); box-shadow: 0 8rpx 20rpx rgba(239, 68, 68, 0.3); }
|
|
|
+.q-chip-fair.q-chip-active { background: linear-gradient(135deg, #FBBF24, #F59E0B); box-shadow: 0 8rpx 20rpx rgba(245, 158, 11, 0.3); }
|
|
|
+.q-chip-good.q-chip-active { background: linear-gradient(135deg, #34D399, #10B981); box-shadow: 0 8rpx 20rpx rgba(16, 185, 129, 0.3); }
|
|
|
+.q-chip-great.q-chip-active { background: linear-gradient(135deg, #818CF8, #6366F1); box-shadow: 0 8rpx 20rpx rgba(99, 102, 241, 0.3); }
|
|
|
+
|
|
|
+.q-scale {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: 14rpx;
|
|
|
+}
|
|
|
+.q-scale-end { font-size: 22rpx; color: #94A3B8; }
|
|
|
+.q-scale-mid { font-size: 20rpx; color: #CBD5E1; }
|
|
|
+
|
|
|
+/* ===== 快速标签 ===== */
|
|
|
+.tag-cloud { display: flex; flex-wrap: wrap; gap: 16rpx; }
|
|
|
+.s-tag {
|
|
|
+ padding: 14rpx 26rpx;
|
|
|
+ border-radius: 999rpx;
|
|
|
+ background: #F8FAFC;
|
|
|
+ border: 2rpx solid #EDF1F7;
|
|
|
+ transition: all 0.18s ease;
|
|
|
+}
|
|
|
+.s-tag text { font-size: 26rpx; color: #475569; }
|
|
|
+.s-tag:active { transform: scale(0.96); }
|
|
|
+.s-tag-active { background: #EEF2FF; border-color: #6366F1; }
|
|
|
+.s-tag-active text { color: #4F46E5; font-weight: 600; }
|
|
|
+
|
|
|
+/* ===== 备注 ===== */
|
|
|
+.remark-input {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 150rpx;
|
|
|
+ background: #F8FAFC;
|
|
|
+ border: 2rpx solid #EDF1F7;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ font-size: 26rpx;
|
|
|
+ color: #1E293B;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.ph-gray { color: #CBD5E1; font-size: 26rpx; }
|
|
|
|
|
|
/* ===== 提交按钮 ===== */
|
|
|
-.submit-wrap { padding: 24rpx 20rpx 32rpx; }
|
|
|
-.submit-btn { background: linear-gradient(135deg, #FF8C42, #F97316); color: #fff; text-align: center; padding: 22rpx 0; border-radius: 14rpx; font-size: 30rpx; font-weight: 600; box-shadow: 0 6rpx 20rpx rgba(249,115,22,0.3); transition: all 0.2s ease; }
|
|
|
-.submit-btn:active { transform: scale(0.98); }
|
|
|
+.submit-wrap { padding: 32rpx 24rpx 8rpx; }
|
|
|
+.submit-btn {
|
|
|
+ background: linear-gradient(135deg, #6366F1, #4F46E5 60%, #4338CA);
|
|
|
+ border-radius: 999rpx;
|
|
|
+ padding: 26rpx 0;
|
|
|
+ text-align: center;
|
|
|
+ box-shadow: 0 12rpx 28rpx rgba(79, 70, 229, 0.35);
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+.submit-btn:active {
|
|
|
+ transform: scale(0.98);
|
|
|
+ box-shadow: 0 6rpx 14rpx rgba(79, 70, 229, 0.25);
|
|
|
+}
|
|
|
+.submit-text { color: #fff; font-size: 30rpx; font-weight: 700; letter-spacing: 2rpx; }
|
|
|
.submitting { opacity: 0.6; }
|
|
|
|
|
|
/* ===== 历史记录 ===== */
|
|
|
-.history-list { display: flex; flex-direction: column; gap: 12rpx; }
|
|
|
-.history-item { display: flex; justify-content: space-between; align-items: center; padding: 14rpx 0; border-bottom: 1rpx solid #f5f5f5; }
|
|
|
-.history-left { flex: 1; min-width: 0; }
|
|
|
-.history-type { font-size: 26rpx; font-weight: 500; color: #333; display: block; }
|
|
|
-.history-content { font-size: 24rpx; color: #888; display: block; margin-top: 4rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
|
-.history-right { text-align: right; margin-left: 16rpx; flex-shrink: 0; }
|
|
|
-.history-date { font-size: 22rpx; color: #999; }
|
|
|
+.history-list { display: flex; flex-direction: column; }
|
|
|
+.history-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 20rpx;
|
|
|
+ padding: 20rpx 0;
|
|
|
+ border-bottom: 2rpx solid #F1F5F9;
|
|
|
+}
|
|
|
+.history-item:last-child { border-bottom: none; }
|
|
|
+.history-badge {
|
|
|
+ width: 76rpx;
|
|
|
+ height: 76rpx;
|
|
|
+ border-radius: 24rpx;
|
|
|
+ background: linear-gradient(135deg, #EEF2FF, #E0E7FF);
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.history-badge-icon { font-size: 36rpx; line-height: 1; }
|
|
|
+.history-main { flex: 1; min-width: 0; }
|
|
|
+.history-line1 { font-size: 28rpx; font-weight: 600; color: #1E293B; display: block; }
|
|
|
+.history-line2 { font-size: 22rpx; color: #64748B; display: block; margin-top: 6rpx; }
|
|
|
+.qbar {
|
|
|
+ height: 8rpx;
|
|
|
+ border-radius: 999rpx;
|
|
|
+ background: #F1F5F9;
|
|
|
+ margin-top: 12rpx;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.qbar-fill { height: 100%; border-radius: 999rpx; transition: width 0.3s ease; }
|
|
|
+.history-date { font-size: 22rpx; color: #94A3B8; flex-shrink: 0; }
|
|
|
|
|
|
/* ===== 底部留白 ===== */
|
|
|
-.bottom-spacer { height: 40rpx; }
|
|
|
+.bottom-spacer { height: 60rpx; }
|
|
|
</style>
|