|
|
@@ -0,0 +1,667 @@
|
|
|
+<template>
|
|
|
+ <view class="container" :style="{ '--font-scale': fontScale }">
|
|
|
+ <login-sheet ref="loginSheet" @success="onLoginSuccess" />
|
|
|
+ <view v-if="!isLoggedIn">
|
|
|
+ <view class="gg-page">
|
|
|
+ <view class="gg-hero">
|
|
|
+ <text class="gg-eyebrow">浠艾福 · 成长历史记录</text>
|
|
|
+ <text class="gg-hero-line gg-hero-line-top">看见改变</text>
|
|
|
+ <text class="gg-hero-line">正在发生</text>
|
|
|
+ <view class="gg-hero-bar"></view>
|
|
|
+ <text class="gg-hero-sub">记录不是目的,看见改变才是。每一份饮食、运动、睡眠、心情的记录,都在为成长提供依据。</text>
|
|
|
+ </view>
|
|
|
+ <view class="gg-guide">
|
|
|
+ <LoginGuideCard
|
|
|
+ title="登录后查看完整成长历史"
|
|
|
+ :items="[
|
|
|
+ '查看饮食、运动、睡眠、心情的所有打卡记录',
|
|
|
+ '查看成长档案测评记录',
|
|
|
+ '筛选分类、按时间排序'
|
|
|
+ ]" />
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-else>
|
|
|
+ <!-- 筛选栏 -->
|
|
|
+ <view class="filter-bar">
|
|
|
+ <view class="filter-tabs">
|
|
|
+ <view
|
|
|
+ class="filter-tab"
|
|
|
+ :class="{ active: currentFilter === 'all' }"
|
|
|
+ @click="setFilter('all')"
|
|
|
+ >
|
|
|
+ 全部
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="filter-tab"
|
|
|
+ :class="{ active: currentFilter === 'diet' }"
|
|
|
+ @click="setFilter('diet')"
|
|
|
+ >
|
|
|
+ 🍽️ 饮食
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="filter-tab"
|
|
|
+ :class="{ active: currentFilter === 'exercise' }"
|
|
|
+ @click="setFilter('exercise')"
|
|
|
+ >
|
|
|
+ 🏃 运动
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="filter-tab"
|
|
|
+ :class="{ active: currentFilter === 'sleep' }"
|
|
|
+ @click="setFilter('sleep')"
|
|
|
+ >
|
|
|
+ 😴 睡眠
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="filter-tab"
|
|
|
+ :class="{ active: currentFilter === 'emotion' }"
|
|
|
+ @click="setFilter('emotion')"
|
|
|
+ >
|
|
|
+ 😊 心情
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="filter-tab"
|
|
|
+ :class="{ active: currentFilter === 'growth' }"
|
|
|
+ @click="setFilter('growth')"
|
|
|
+ >
|
|
|
+ 📈 成长档案
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 记录列表 -->
|
|
|
+ <view class="records-section">
|
|
|
+ <view v-if="loading" class="loading-state">
|
|
|
+ <view class="loading-spinner"></view>
|
|
|
+ <text class="loading-text">加载中...</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-else-if="filteredRecords.length === 0" class="empty-state">
|
|
|
+ <text class="empty-icon">📭</text>
|
|
|
+ <text class="empty-title">暂无记录</text>
|
|
|
+ <text class="empty-desc">{{ emptyMessage }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view v-else class="records-list">
|
|
|
+ <view
|
|
|
+ class="record-card"
|
|
|
+ v-for="(record, index) in filteredRecords"
|
|
|
+ :key="getRecordKey(record, index)"
|
|
|
+ @click="goToDetail(record)"
|
|
|
+ >
|
|
|
+ <view class="record-type-badge" :class="'type-' + record.type">{{ record.typeLabel }}</view>
|
|
|
+ <view class="record-content">
|
|
|
+ <view class="record-header">
|
|
|
+ <text class="record-title">{{ record.title }}</text>
|
|
|
+ <text class="record-date">{{ record.formattedDate }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="record-meta" v-if="record.meta">
|
|
|
+ <text class="meta-item" v-for="(item, i) in record.meta" :key="i">
|
|
|
+ {{ item }}
|
|
|
+ </text>
|
|
|
+ </view>
|
|
|
+ <view class="record-summary" v-if="record.summary">
|
|
|
+ {{ record.summary }}
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <text class="record-chevron">›</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 加载更多 -->
|
|
|
+ <view class="load-more" v-if="!loading && hasMore && filteredRecords.length > 0" @click="loadMore">
|
|
|
+ <text class="load-more-text">加载更多</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import LoginGuideCard from '../../components/LoginGuideCard.vue'
|
|
|
+import {
|
|
|
+ healthCheckinList,
|
|
|
+ getHealthMealList,
|
|
|
+ getExerciseRecords,
|
|
|
+ getSleepRecords,
|
|
|
+ getEmotionCheckinList,
|
|
|
+ getGrowthRecordList,
|
|
|
+ getGrowthRecordDetail
|
|
|
+} from '../../utils/api.js'
|
|
|
+import { parseDate, recordDateOf } from '../../utils/format.js'
|
|
|
+import fontScaleMixin from '@/mixins/font-scale.js'
|
|
|
+
|
|
|
+export default {
|
|
|
+ mixins: [fontScaleMixin],
|
|
|
+ components: { LoginGuideCard },
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ memberId: '',
|
|
|
+ isLoggedIn: false,
|
|
|
+ currentFilter: 'all',
|
|
|
+ loading: false,
|
|
|
+ loadingMore: false,
|
|
|
+ page: 1,
|
|
|
+ pageSize: 20,
|
|
|
+ hasMore: true,
|
|
|
+ // 原始数据
|
|
|
+ allDietRecords: [],
|
|
|
+ allExerciseRecords: [],
|
|
|
+ allSleepRecords: [],
|
|
|
+ allEmotionRecords: [],
|
|
|
+ allGrowthRecords: [],
|
|
|
+ // 合并后的记录(用于显示)
|
|
|
+ mergedRecords: [],
|
|
|
+ }
|
|
|
+ },
|
|
|
+ computed: {
|
|
|
+ filteredRecords() {
|
|
|
+ if (this.currentFilter === 'all') {
|
|
|
+ return this.mergedRecords
|
|
|
+ }
|
|
|
+ return this.mergedRecords.filter(r => r.type === this.currentFilter)
|
|
|
+ },
|
|
|
+ emptyMessage() {
|
|
|
+ const messages = {
|
|
|
+ all: '暂无任何成长记录,快去打卡吧!',
|
|
|
+ diet: '暂无饮食记录',
|
|
|
+ exercise: '暂无运动记录',
|
|
|
+ sleep: '暂无睡眠记录',
|
|
|
+ emotion: '暂无心情记录',
|
|
|
+ growth: '暂无成长档案记录'
|
|
|
+ }
|
|
|
+ return messages[this.currentFilter] || messages.all
|
|
|
+ }
|
|
|
+ },
|
|
|
+ onLoad() {
|
|
|
+ // 初始化时不加载,等 onShow
|
|
|
+ },
|
|
|
+ onShow() {
|
|
|
+ this.isLoggedIn = !!uni.getStorageSync('token')
|
|
|
+ if (this.isLoggedIn) {
|
|
|
+ this.memberId = uni.getStorageSync('currentChildId') || uni.getStorageSync('currentMemberId') || uni.getStorageSync('userId') || ''
|
|
|
+ if (!this._dataLoaded) {
|
|
|
+ this._dataLoaded = true
|
|
|
+ this.loadAllData()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ methods: {
|
|
|
+ setFilter(filter) {
|
|
|
+ this.currentFilter = filter
|
|
|
+ },
|
|
|
+ async loadAllData() {
|
|
|
+ this.loading = true
|
|
|
+ try {
|
|
|
+ // 并行加载所有类型的记录
|
|
|
+ await Promise.all([
|
|
|
+ this.loadDietRecords(),
|
|
|
+ this.loadExerciseRecords(),
|
|
|
+ this.loadSleepRecords(),
|
|
|
+ this.loadEmotionRecords(),
|
|
|
+ this.loadGrowthRecords()
|
|
|
+ ])
|
|
|
+ this.mergeAndSortRecords()
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载成长历史失败', e)
|
|
|
+ uni.showToast({ title: '加载失败', icon: 'none' })
|
|
|
+ } finally {
|
|
|
+ this.loading = false
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async loadDietRecords() {
|
|
|
+ try {
|
|
|
+ const res = await getHealthMealList(this.memberId ? { memberId: this.memberId } : {})
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ this.allDietRecords = res.data.map(item => this.formatDietRecord(item))
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载饮食记录失败', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async loadExerciseRecords() {
|
|
|
+ try {
|
|
|
+ const res = await getExerciseRecords(this.memberId ? { memberId: this.memberId } : {})
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ this.allExerciseRecords = res.data.map(item => this.formatExerciseRecord(item))
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载运动记录失败', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async loadSleepRecords() {
|
|
|
+ try {
|
|
|
+ const res = await getSleepRecords(this.memberId ? { memberId: this.memberId } : {})
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ this.allSleepRecords = res.data.map(item => this.formatSleepRecord(item))
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载睡眠记录失败', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async loadEmotionRecords() {
|
|
|
+ try {
|
|
|
+ const res = await getEmotionCheckinList({ page: 1, size: 100, memberId: this.memberId })
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ this.allEmotionRecords = res.data.map(item => this.formatEmotionRecord(item))
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载心情记录失败', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async loadGrowthRecords() {
|
|
|
+ try {
|
|
|
+ const res = await getGrowthRecordList()
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ this.allGrowthRecords = res.data.map(item => this.formatGrowthRecord(item))
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('加载成长档案失败', e)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ formatDietRecord(item) {
|
|
|
+ const date = recordDateOf(item)
|
|
|
+ return {
|
|
|
+ id: item.id,
|
|
|
+ type: 'diet',
|
|
|
+ typeLabel: '饮食',
|
|
|
+ title: this.mealTypeLabel(item.mealType) || '饮食记录',
|
|
|
+ date: date,
|
|
|
+ formattedDate: date ? this.formatDisplayDate(date) : '',
|
|
|
+ meta: [
|
|
|
+ item.foods && item.foods.length > 0 ? `包含 ${item.foods.length} 种食物` : '',
|
|
|
+ item.totalCalories ? `约 ${item.totalCalories} kcal` : ''
|
|
|
+ ].filter(Boolean),
|
|
|
+ summary: item.remark || '',
|
|
|
+ rawData: item
|
|
|
+ }
|
|
|
+ },
|
|
|
+ formatExerciseRecord(item) {
|
|
|
+ const date = recordDateOf(item)
|
|
|
+ return {
|
|
|
+ id: item.id,
|
|
|
+ type: 'exercise',
|
|
|
+ typeLabel: '运动',
|
|
|
+ title: item.exerciseName || '运动记录',
|
|
|
+ date: date,
|
|
|
+ formattedDate: date ? this.formatDisplayDate(date) : '',
|
|
|
+ meta: [
|
|
|
+ item.duration ? `时长 ${item.duration} 分钟` : '',
|
|
|
+ item.calories ? `消耗 ${item.calories} kcal` : ''
|
|
|
+ ].filter(Boolean),
|
|
|
+ summary: item.remark || '',
|
|
|
+ rawData: item
|
|
|
+ }
|
|
|
+ },
|
|
|
+ formatSleepRecord(item) {
|
|
|
+ const date = recordDateOf(item)
|
|
|
+ return {
|
|
|
+ id: item.id,
|
|
|
+ type: 'sleep',
|
|
|
+ typeLabel: '睡眠',
|
|
|
+ title: '睡眠记录',
|
|
|
+ date: date,
|
|
|
+ formattedDate: date ? this.formatDisplayDate(date) : '',
|
|
|
+ meta: [
|
|
|
+ item.startTime && item.endTime ? `${this.formatTime(item.startTime)} - ${this.formatTime(item.endTime)}` : '',
|
|
|
+ item.duration ? `时长 ${this.formatDuration(item.duration)}` : '',
|
|
|
+ item.quality ? `质量 ${item.quality}` : ''
|
|
|
+ ].filter(Boolean),
|
|
|
+ summary: item.remark || '',
|
|
|
+ rawData: item
|
|
|
+ }
|
|
|
+ },
|
|
|
+ formatEmotionRecord(item) {
|
|
|
+ const date = recordDateOf(item)
|
|
|
+ return {
|
|
|
+ id: item.id,
|
|
|
+ type: 'emotion',
|
|
|
+ typeLabel: '心情',
|
|
|
+ title: item.emotionLabel || '心情记录',
|
|
|
+ date: date,
|
|
|
+ formattedDate: date ? this.formatDisplayDate(date) : '',
|
|
|
+ meta: [
|
|
|
+ item.intensity ? `强度 ${item.intensity}` : '',
|
|
|
+ item.tags && item.tags.length > 0 ? `标签: ${item.tags.join('、')}` : ''
|
|
|
+ ].filter(Boolean),
|
|
|
+ summary: item.note || '',
|
|
|
+ rawData: item
|
|
|
+ }
|
|
|
+ },
|
|
|
+ formatGrowthRecord(item) {
|
|
|
+ const date = item.reportDate || item.createdAt
|
|
|
+ return {
|
|
|
+ id: item.id,
|
|
|
+ type: 'growth',
|
|
|
+ typeLabel: '成长档案',
|
|
|
+ title: item.recordTitle || '成长档案',
|
|
|
+ date: date,
|
|
|
+ formattedDate: date ? this.formatDisplayDate(date) : '',
|
|
|
+ meta: [
|
|
|
+ item.overallScore != null ? `综合得分: ${item.overallScore}` : '',
|
|
|
+ item.sourceType ? `来源: ${this.sourceTypeLabel(item.sourceType)}` : ''
|
|
|
+ ].filter(Boolean),
|
|
|
+ summary: item.assessmentSummary || item.growthSuggestions || '',
|
|
|
+ rawData: item
|
|
|
+ }
|
|
|
+ },
|
|
|
+ mergeAndSortRecords() {
|
|
|
+ const allRecords = [
|
|
|
+ ...this.allDietRecords,
|
|
|
+ ...this.allExerciseRecords,
|
|
|
+ ...this.allSleepRecords,
|
|
|
+ ...this.allEmotionRecords,
|
|
|
+ ...this.allGrowthRecords
|
|
|
+ ]
|
|
|
+ // 按日期倒序排序
|
|
|
+ allRecords.sort((a, b) => {
|
|
|
+ if (!a.date && !b.date) return 0
|
|
|
+ if (!a.date) return 1
|
|
|
+ if (!b.date) return -1
|
|
|
+ return new Date(b.date) - new Date(a.date)
|
|
|
+ })
|
|
|
+ this.mergedRecords = allRecords
|
|
|
+ },
|
|
|
+ formatDisplayDate(dateStr) {
|
|
|
+ if (!dateStr) return ''
|
|
|
+ const d = parseDate(dateStr)
|
|
|
+ if (!d) return dateStr.substring(0, 10)
|
|
|
+ const month = d.getMonth() + 1
|
|
|
+ const day = d.getDate()
|
|
|
+ const hour = d.getHours()
|
|
|
+ const minute = d.getMinutes()
|
|
|
+ return `${month}月${day}日 ${this.pad(hour)}:${this.pad(minute)}`
|
|
|
+ },
|
|
|
+ formatTime(timeStr) {
|
|
|
+ if (!timeStr) return ''
|
|
|
+ const d = parseDate(timeStr)
|
|
|
+ if (!d) return timeStr.substring(11, 16)
|
|
|
+ return `${this.pad(d.getHours())}:${this.pad(d.getMinutes())}`
|
|
|
+ },
|
|
|
+ formatDuration(minutes) {
|
|
|
+ if (!minutes) return ''
|
|
|
+ const h = Math.floor(minutes / 60)
|
|
|
+ const m = minutes % 60
|
|
|
+ return h > 0 ? `${h}小时${m}分` : `${m}分钟`
|
|
|
+ },
|
|
|
+ mealTypeLabel(type) {
|
|
|
+ const m = { breakfast: '早餐', lunch: '午餐', dinner: '晚餐', snack: '加餐', extra: '零食' }
|
|
|
+ if (!type) return '饮食'
|
|
|
+ const parts = String(type).split(',')
|
|
|
+ const labels = []
|
|
|
+ for (let i = 0; i < parts.length; i++) {
|
|
|
+ const v = parts[i] && parts[i].trim()
|
|
|
+ if (!v) continue
|
|
|
+ labels.push(m[v] || v)
|
|
|
+ }
|
|
|
+ return labels.length > 0 ? labels.join('、') : '饮食'
|
|
|
+ },
|
|
|
+ sourceTypeLabel(type) {
|
|
|
+ const map = { manual: '手动创建', assessment: '测评录入', upload: '报告上传', supplement: '补充材料' }
|
|
|
+ return map[type] || type
|
|
|
+ },
|
|
|
+ pad(n) {
|
|
|
+ return n < 10 ? '0' + n : '' + n
|
|
|
+ },
|
|
|
+ getRecordKey(record, index) {
|
|
|
+ return record.id || ('idx-' + index)
|
|
|
+ },
|
|
|
+ goToDetail(record) {
|
|
|
+ if (!record) return
|
|
|
+ const type = record.type
|
|
|
+ const raw = record.rawData
|
|
|
+ let url = ''
|
|
|
+
|
|
|
+ if (type === 'diet') {
|
|
|
+ url = '/pages/growth/diet-checkin?recordId=' + raw.id + '&viewOnly=1'
|
|
|
+ } else if (type === 'exercise') {
|
|
|
+ url = '/pages/growth/exercise-checkin?recordId=' + raw.id + '&viewOnly=1'
|
|
|
+ } else if (type === 'sleep') {
|
|
|
+ url = '/pages/growth/sleep-checkin?recordId=' + raw.id + '&viewOnly=1'
|
|
|
+ } else if (type === 'emotion') {
|
|
|
+ url = '/pages/mind-detail/emotion-checkin?recordId=' + raw.id + '&viewOnly=1'
|
|
|
+ } else if (type === 'growth') {
|
|
|
+ url = '/pages/growth/detail?recordId=' + raw.id
|
|
|
+ }
|
|
|
+
|
|
|
+ if (url && this.memberId) {
|
|
|
+ url += (url.includes('?') ? '&' : '?') + 'memberId=' + this.memberId
|
|
|
+ }
|
|
|
+
|
|
|
+ if (url) {
|
|
|
+ uni.navigateTo({ url })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ async loadMore() {
|
|
|
+ // 暂时不实现分页加载,因为已经一次性加载了所有数据
|
|
|
+ uni.showToast({ title: '已加载全部记录', icon: 'none' })
|
|
|
+ },
|
|
|
+ onLoginSuccess() {
|
|
|
+ this.isLoggedIn = true
|
|
|
+ this.memberId = uni.getStorageSync('currentChildId') || uni.getStorageSync('currentMemberId') || uni.getStorageSync('userId') || ''
|
|
|
+ this._dataLoaded = false
|
|
|
+ this.loadAllData()
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.container {
|
|
|
+ background: $facade-brand-tint;
|
|
|
+ min-height: 100vh;
|
|
|
+ padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
|
|
|
+}
|
|
|
+
|
|
|
+// ==================== 游客态 ====================
|
|
|
+.gg-page { background: $app-bg; min-height: 100vh; padding: 28rpx 0 40rpx; }
|
|
|
+
|
|
|
+.gg-hero { margin: 0 32rpx 28rpx; display: flex; flex-direction: column; }
|
|
|
+.gg-eyebrow { font-size: calc(22rpx * var(--font-scale, 1)); font-weight: 600; color: $brand-primary; letter-spacing: 2rpx; }
|
|
|
+.gg-hero-line { font-size: calc(52rpx * var(--font-scale, 1)); font-weight: 700; color: $facade-text-strong; line-height: 1.28; }
|
|
|
+.gg-hero-line-top { margin-top: 16rpx; }
|
|
|
+.gg-hero-bar { width: 84rpx; height: 8rpx; border-radius: 999rpx; margin: 20rpx 0 18rpx; background: linear-gradient(135deg, $brand-primary, $brand-primary-dark); }
|
|
|
+.gg-hero-sub { font-size: calc(24rpx * var(--font-scale, 1)); color: $facade-text-body; line-height: 1.7; }
|
|
|
+
|
|
|
+.gg-guide { margin: 0 32rpx 8rpx; }
|
|
|
+
|
|
|
+// ==================== 已登录态 ====================
|
|
|
+.filter-bar {
|
|
|
+ position: sticky;
|
|
|
+ top: 0;
|
|
|
+ z-index: 100;
|
|
|
+ background: $app-surface;
|
|
|
+ border-bottom: 1rpx solid $app-border-subtle;
|
|
|
+ padding: 16rpx 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-tabs {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12rpx;
|
|
|
+ justify-content: flex-start;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-tab {
|
|
|
+ font-size: calc(24rpx * var(--font-scale, 1));
|
|
|
+ padding: 8rpx 16rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ background: $facade-fill-2;
|
|
|
+ color: $facade-text-body;
|
|
|
+ white-space: nowrap;
|
|
|
+ transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-tab.active {
|
|
|
+ background: linear-gradient(135deg, $brand-primary, $brand-primary-dark);
|
|
|
+ color: $app-surface;
|
|
|
+}
|
|
|
+
|
|
|
+.filter-tab:active {
|
|
|
+ opacity: 0.85;
|
|
|
+ transform: scale(0.98);
|
|
|
+}
|
|
|
+
|
|
|
+.records-section {
|
|
|
+ padding: 0 24rpx 40rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-state {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding: 80rpx 0;
|
|
|
+}
|
|
|
+
|
|
|
+.loading-spinner {
|
|
|
+ width: 48rpx;
|
|
|
+ height: 48rpx;
|
|
|
+ border: 3rpx solid $facade-fill-2;
|
|
|
+ border-top-color: $brand-primary;
|
|
|
+ border-radius: 50%;
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes spin {
|
|
|
+ to { transform: rotate(360deg); }
|
|
|
+}
|
|
|
+
|
|
|
+.loading-text {
|
|
|
+ margin-top: 20rpx;
|
|
|
+ font-size: calc(26rpx * var(--font-scale, 1));
|
|
|
+ color: $facade-text-muted;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-state {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding: 100rpx 0;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-icon {
|
|
|
+ font-size: calc(80rpx * var(--font-scale, 1));
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-title {
|
|
|
+ font-size: calc(30rpx * var(--font-scale, 1));
|
|
|
+ font-weight: 600;
|
|
|
+ color: $facade-text-strong;
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.empty-desc {
|
|
|
+ font-size: calc(24rpx * var(--font-scale, 1));
|
|
|
+ color: $facade-text-muted;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.records-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.record-card {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: flex-start;
|
|
|
+ background: $app-surface;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 20rpx;
|
|
|
+ border: 1rpx solid $app-border-subtle;
|
|
|
+ box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.record-card:active {
|
|
|
+ opacity: 0.85;
|
|
|
+ background: $facade-fill-2;
|
|
|
+}
|
|
|
+
|
|
|
+.record-type-badge {
|
|
|
+ font-size: calc(20rpx * var(--font-scale, 1));
|
|
|
+ font-weight: 600;
|
|
|
+ padding: 4rpx 10rpx;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ margin-right: 16rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-top: 4rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.type-diet { background: $dim-body-tint; color: $dim-body-deep; }
|
|
|
+.type-exercise { background: $dim-action-tint; color: $dim-action-deep; }
|
|
|
+.type-sleep { background: $dim-wisdom-tint; color: $dim-wisdom-deep; }
|
|
|
+.type-emotion { background: $dim-mind-tint; color: $dim-mind-deep; }
|
|
|
+.type-growth { background: $accent-indigo-tint; color: $accent-indigo; }
|
|
|
+
|
|
|
+.record-content {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.record-header {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: flex-start;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.record-title {
|
|
|
+ font-size: calc(28rpx * var(--font-scale, 1));
|
|
|
+ font-weight: 600;
|
|
|
+ color: $facade-text-strong;
|
|
|
+ flex: 1;
|
|
|
+ margin-right: 12rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.record-date {
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
+ color: $facade-text-muted;
|
|
|
+ flex-shrink: 0;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.record-meta {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 16rpx;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.meta-item {
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
+ color: $facade-text-body;
|
|
|
+}
|
|
|
+
|
|
|
+.record-summary {
|
|
|
+ font-size: calc(22rpx * var(--font-scale, 1));
|
|
|
+ color: $facade-text-muted;
|
|
|
+ line-height: 1.5;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ display: -webkit-box;
|
|
|
+ -webkit-line-clamp: 2;
|
|
|
+ -webkit-box-orient: vertical;
|
|
|
+}
|
|
|
+
|
|
|
+.record-chevron {
|
|
|
+ font-size: calc(32rpx * var(--font-scale, 1));
|
|
|
+ color: $facade-text-ghost;
|
|
|
+ margin-left: 12rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.load-more {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 32rpx 0;
|
|
|
+}
|
|
|
+
|
|
|
+.load-more-text {
|
|
|
+ font-size: calc(26rpx * var(--font-scale, 1));
|
|
|
+ color: $brand-primary;
|
|
|
+}
|
|
|
+</style>
|