|
|
@@ -34,7 +34,24 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 情绪日历 -->
|
|
|
+ <!-- Phase 1.2: 情绪风险告警 -->
|
|
|
+ <PsychCrisisBanner v-if="hasChildId" :childId="currentChildId" source="checkin" />
|
|
|
+
|
|
|
+ <!-- ===== Phase 1.1: 情绪评分折线图 ===== -->
|
|
|
+ <view class="chart-card" v-if="moodTrendData.length > 0">
|
|
|
+ <text class="card-title">情绪评分趋势</text>
|
|
|
+ <text class="card-subtitle" v-if="trend && trend.moodScoreAvg">周均分: {{ trend.moodScoreAvg }}</text>
|
|
|
+ <canvas class="line-chart-canvas" canvas-id="moodTrendCanvas" @ready="drawMoodTrend"></canvas>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- ===== Phase 1.1: 效价-激活度散点图 ===== -->
|
|
|
+ <view class="chart-card" v-if="scatterData.length > 0">
|
|
|
+ <text class="card-title">情绪空间分布</text>
|
|
|
+ <text class="card-subtitle">每个点代表一次打卡</text>
|
|
|
+ <canvas class="scatter-canvas" canvas-id="scatterCanvas" @ready="drawScatter"></canvas>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- ===== 情绪日历(升级为热力版) ===== -->
|
|
|
<view class="calendar-card">
|
|
|
<text class="card-title">情绪日历</text>
|
|
|
<view class="calendar-grid">
|
|
|
@@ -44,10 +61,25 @@
|
|
|
<view
|
|
|
v-for="(day, idx) in calendarDays"
|
|
|
:key="idx"
|
|
|
- :class="['calendar-day', 'weather-' + (day.weather || 'none'), day.isToday ? 'is-today' : '']">
|
|
|
+ :class="['calendar-day', day.isToday ? 'is-today' : '', day.moodScore ? 'has-data' : '']"
|
|
|
+ :style="day.bgStyle"
|
|
|
+ @click="day.moodScore && showDayDetail(day)">
|
|
|
<text class="day-num">{{ day.dayNum }}</text>
|
|
|
- <text v-if="day.weather" class="day-emoji">{{ weatherEmoji(day.weather) }}</text>
|
|
|
+ <text v-if="day.weather" class="day-emoji-small">{{ weatherEmoji(day.weather) }}</text>
|
|
|
+ <text v-if="day.moodScore" class="day-score">{{ day.moodScore }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!-- 热力图例 -->
|
|
|
+ <view class="heatmap-legend">
|
|
|
+ <text class="legend-label">低</text>
|
|
|
+ <view class="legend-bar">
|
|
|
+ <view class="legend-step" style="background:#FFE0E0"></view>
|
|
|
+ <view class="legend-step" style="background:#FFB0B0"></view>
|
|
|
+ <view class="legend-step" style="background:#FFD9A0"></view>
|
|
|
+ <view class="legend-step" style="background:#C8E6C9"></view>
|
|
|
+ <view class="legend-step" style="background:#81C784"></view>
|
|
|
</view>
|
|
|
+ <text class="legend-label">高</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
@@ -66,13 +98,19 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 按天详情 -->
|
|
|
+ <!-- 按天详情(含情绪精细数据) -->
|
|
|
<view class="daily-list-card" v-if="dailyData.length > 0">
|
|
|
<text class="card-title">每日记录</text>
|
|
|
- <view class="daily-item" v-for="(day, idx) in dailyData" :key="idx">
|
|
|
+ <view class="daily-item" v-for="(day, idx) in dailyData" :key="idx" @click="viewDayCheckin(day)">
|
|
|
<text class="daily-date">{{ formatDate(day.date) }}</text>
|
|
|
<text class="daily-emoji">{{ weatherEmoji(day.weather) }}</text>
|
|
|
<text class="daily-weather">{{ weatherLabel(day.weather) }}</text>
|
|
|
+ <!-- Phase 1.1 精细数据标签 -->
|
|
|
+ <view class="daily-tags" v-if="day.moodScore">
|
|
|
+ <text class="daily-tag score-tag">{{ day.moodScore }}分</text>
|
|
|
+ <text class="daily-tag type-tag" v-if="day.emotionType">{{ emotionTypeLabel(day.emotionType) }}</text>
|
|
|
+ <text class="daily-tag arousal-tag" v-if="day.arousalLevel">激活{{ day.arousalLevel }}</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
@@ -86,10 +124,13 @@
|
|
|
|
|
|
<script>
|
|
|
import config from '../../config'
|
|
|
+import PsychCrisisBanner from '../../components/PsychCrisisBanner.vue'
|
|
|
|
|
|
export default {
|
|
|
+ components: { PsychCrisisBanner },
|
|
|
data() {
|
|
|
return {
|
|
|
+ currentChildId: null,
|
|
|
period: 'week',
|
|
|
stats: null,
|
|
|
trend: null,
|
|
|
@@ -100,11 +141,27 @@ export default {
|
|
|
rainy: { icon: '\uD83C\uDF27\uFE0F', label: '有点难过', color: '#6B9BD2' },
|
|
|
stormy: { icon: '\u26A1', label: '很烦躁', color: '#8B5CF6' },
|
|
|
rainbow: { icon: '\uD83C\uDF08', label: '特别开心', color: '#FF6B9D' }
|
|
|
- }
|
|
|
+ },
|
|
|
+ // Plutchik 情绪中文映射
|
|
|
+ emotionTypeLabels: {
|
|
|
+ 'joy': '开心', 'trust': '信任', 'fear': '恐惧', 'surprise': '惊讶',
|
|
|
+ 'sadness': '悲伤', 'disgust': '厌恶', 'anger': '愤怒', 'anticipation': '期待'
|
|
|
+ },
|
|
|
+
|
|
|
+ // 散点/折线图数据
|
|
|
+ scatterData: [],
|
|
|
+ moodTrendData: [],
|
|
|
+
|
|
|
+ // Canvas 上下文
|
|
|
+ scatterCtx: null,
|
|
|
+ trendCtx: null
|
|
|
}
|
|
|
},
|
|
|
|
|
|
computed: {
|
|
|
+ hasChildId() {
|
|
|
+ return !!this.currentChildId
|
|
|
+ },
|
|
|
calendarDays() {
|
|
|
const days = []
|
|
|
const now = new Date()
|
|
|
@@ -114,27 +171,59 @@ export default {
|
|
|
const daysInMonth = new Date(year, month + 1, 0).getDate()
|
|
|
const today = now.getDate()
|
|
|
|
|
|
- // Build weather map from daily data
|
|
|
- const weatherMap = {}
|
|
|
+ // Build data map from dailyData
|
|
|
+ const dataMap = {}
|
|
|
if (this.trend && this.trend.dailyData) {
|
|
|
for (const d of this.trend.dailyData) {
|
|
|
if (d.date) {
|
|
|
const dateObj = new Date(d.date)
|
|
|
- weatherMap[dateObj.getDate()] = d.weather
|
|
|
+ dataMap[dateObj.getDate()] = d
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Empty leading cells
|
|
|
for (let i = 0; i < firstDay; i++) {
|
|
|
- days.push({ dayNum: '', weather: null, isToday: false })
|
|
|
+ days.push({ dayNum: '', weather: null, moodScore: null, isToday: false, bgStyle: '' })
|
|
|
}
|
|
|
|
|
|
for (let d = 1; d <= daysInMonth; d++) {
|
|
|
+ const data = dataMap[d]
|
|
|
+ var bgStyle = ''
|
|
|
+ if (data && data.moodScore) {
|
|
|
+ // Heatmap intensity: moodScore 1-10 mapped to color
|
|
|
+ var score = data.moodScore
|
|
|
+ var r, g, b
|
|
|
+ if (score <= 3) {
|
|
|
+ // 1-3: red tint
|
|
|
+ var intensity1 = (score - 1) / 2
|
|
|
+ r = 255
|
|
|
+ g = Math.round(180 + 75 * intensity1)
|
|
|
+ b = Math.round(180 + 75 * intensity1)
|
|
|
+ } else if (score <= 6) {
|
|
|
+ // 4-6: orange/yellow tint
|
|
|
+ var intensity2 = (score - 4) / 2
|
|
|
+ r = 255
|
|
|
+ g = Math.round(200 + 55 * intensity2)
|
|
|
+ b = Math.round(160 - 60 * intensity2)
|
|
|
+ } else {
|
|
|
+ // 7-10: green tint
|
|
|
+ var intensity3 = (score - 7) / 3
|
|
|
+ r = Math.round(200 - 120 * intensity3)
|
|
|
+ g = Math.round(230 - 20 * intensity3)
|
|
|
+ b = Math.round(200 - 120 * intensity3)
|
|
|
+ }
|
|
|
+ bgStyle = 'background: rgba(' + r + ',' + g + ',' + b + ',0.4)'
|
|
|
+ }
|
|
|
+
|
|
|
days.push({
|
|
|
dayNum: d,
|
|
|
- weather: weatherMap[d] || null,
|
|
|
- isToday: d === today
|
|
|
+ weather: data ? data.weather : null,
|
|
|
+ moodScore: data ? data.moodScore : null,
|
|
|
+ emotionType: data ? data.emotionType : null,
|
|
|
+ arousalLevel: data ? data.arousalLevel : null,
|
|
|
+ isToday: d === today,
|
|
|
+ bgStyle: bgStyle
|
|
|
})
|
|
|
}
|
|
|
|
|
|
@@ -166,6 +255,8 @@ export default {
|
|
|
},
|
|
|
|
|
|
onLoad() {
|
|
|
+ const app = getApp()
|
|
|
+ this.currentChildId = app.globalData.currentChildId || null
|
|
|
this.loadData()
|
|
|
},
|
|
|
|
|
|
@@ -193,6 +284,14 @@ export default {
|
|
|
success: (res) => {
|
|
|
if (res.data && res.data.code === 200) {
|
|
|
this.trend = res.data.data
|
|
|
+ // Extract scatter & trend data
|
|
|
+ this.scatterData = this.trend && this.trend.scatterData || []
|
|
|
+ this.moodTrendData = this.trend && this.trend.moodTrend || []
|
|
|
+ // Draw charts after data is ready
|
|
|
+ this.$nextTick(function() {
|
|
|
+ this.drawMoodTrend()
|
|
|
+ this.drawScatter()
|
|
|
+ })
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
@@ -213,6 +312,10 @@ export default {
|
|
|
return cfg ? cfg.label : weather
|
|
|
},
|
|
|
|
|
|
+ emotionTypeLabel(type) {
|
|
|
+ return this.emotionTypeLabels[type] || type
|
|
|
+ },
|
|
|
+
|
|
|
formatDate(dateStr) {
|
|
|
if (!dateStr) return ''
|
|
|
const d = new Date(dateStr)
|
|
|
@@ -221,6 +324,222 @@ export default {
|
|
|
|
|
|
goCheckin() {
|
|
|
uni.navigateTo({ url: '/pages/mind/emotion-checkin' })
|
|
|
+ },
|
|
|
+
|
|
|
+ showDayDetail(day) {
|
|
|
+ // 点击日历格子可以跳转到当天的打卡详情
|
|
|
+ // 简单 toast 显示评分
|
|
|
+ uni.showToast({ title: '情绪评分: ' + day.moodScore + '/10', icon: 'none' })
|
|
|
+ },
|
|
|
+
|
|
|
+ viewDayCheckin(day) {
|
|
|
+ // 查看单条打卡详情(当前简单响应)
|
|
|
+ if (day.moodScore) {
|
|
|
+ uni.showToast({ title: '情绪评分: ' + day.moodScore + '/10', icon: 'none' })
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // ===== Canvas 绘图 =====
|
|
|
+
|
|
|
+ drawMoodTrend() {
|
|
|
+ if (this.moodTrendData.length < 1) return
|
|
|
+
|
|
|
+ var canvasId = 'moodTrendCanvas'
|
|
|
+ var query = uni.createSelectorQuery()
|
|
|
+ var self = this
|
|
|
+
|
|
|
+ query.select('#' + canvasId).fields({ node: true, size: true }).exec(function(res) {
|
|
|
+ // Fallback: use context API for older uni-app
|
|
|
+ var ctx = uni.createCanvasContext(canvasId, self)
|
|
|
+ var dpr = 2
|
|
|
+ // Get canvas dimensions from the element
|
|
|
+ query.select('#' + canvasId).boundingClientRect(function(rect) {
|
|
|
+ if (!rect) return
|
|
|
+ var width = rect.width || 300
|
|
|
+ var height = rect.height || 200
|
|
|
+ var canvasWidth = width * dpr
|
|
|
+ var canvasHeight = height * dpr
|
|
|
+
|
|
|
+ // Clear
|
|
|
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight)
|
|
|
+
|
|
|
+ var data = self.moodTrendData
|
|
|
+ var count = data.length
|
|
|
+ if (count === 0) return
|
|
|
+
|
|
|
+ var padding = { top: 20 * dpr, right: 20 * dpr, bottom: 30 * dpr, left: 30 * dpr }
|
|
|
+ var plotW = canvasWidth - padding.left - padding.right
|
|
|
+ var plotH = canvasHeight - padding.top - padding.bottom
|
|
|
+
|
|
|
+ // Draw axes
|
|
|
+ ctx.setStrokeStyle('#E0E0E0')
|
|
|
+ ctx.setLineWidth(1 * dpr)
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(padding.left, padding.top)
|
|
|
+ ctx.lineTo(padding.left, padding.top + plotH)
|
|
|
+ ctx.lineTo(padding.left + plotW, padding.top + plotH)
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ // Draw Y axis labels
|
|
|
+ ctx.setFontSize(10 * dpr)
|
|
|
+ ctx.setFillStyle('#999')
|
|
|
+ ctx.setTextAlign('right')
|
|
|
+ for (var yVal = 2; yVal <= 10; yVal += 2) {
|
|
|
+ var yPos = padding.top + plotH - (yVal / 10) * plotH
|
|
|
+ ctx.fillText(yVal + '', padding.left - 6 * dpr, yPos + 3 * dpr)
|
|
|
+ // Grid line
|
|
|
+ ctx.setStrokeStyle('#F0F0F0')
|
|
|
+ ctx.setLineWidth(1 * dpr)
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(padding.left, yPos)
|
|
|
+ ctx.lineTo(padding.left + plotW, yPos)
|
|
|
+ ctx.stroke()
|
|
|
+ }
|
|
|
+
|
|
|
+ // Determine trend line color based on trend direction
|
|
|
+ var avg = 0
|
|
|
+ for (var si = 0; si < count; si++) {
|
|
|
+ avg += data[si].moodScore
|
|
|
+ }
|
|
|
+ avg /= count
|
|
|
+ var lineColor = avg >= 5 ? '#10B981' : '#FF6B9D'
|
|
|
+
|
|
|
+ // Draw data points and lines
|
|
|
+ ctx.setStrokeStyle(lineColor)
|
|
|
+ ctx.setLineWidth(3 * dpr)
|
|
|
+ ctx.setFillStyle(lineColor)
|
|
|
+
|
|
|
+ for (var i = 0; i < count; i++) {
|
|
|
+ var x = padding.left + (i / Math.max(count - 1, 1)) * plotW
|
|
|
+ var y = padding.top + plotH - (data[i].moodScore / 10) * plotH
|
|
|
+
|
|
|
+ if (i === 0) {
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(x, y)
|
|
|
+ } else {
|
|
|
+ ctx.lineTo(x, y)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Draw point circle
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(x, y, 4 * dpr, 0, 2 * Math.PI)
|
|
|
+ ctx.setFillStyle(lineColor)
|
|
|
+ ctx.fill()
|
|
|
+ }
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ // Draw X axis labels (dates)
|
|
|
+ ctx.setFontSize(9 * dpr)
|
|
|
+ ctx.setFillStyle('#999')
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ var step = Math.max(1, Math.floor(count / 6))
|
|
|
+ for (var di = 0; di < count; di += step) {
|
|
|
+ var labelX = padding.left + (di / Math.max(count - 1, 1)) * plotW
|
|
|
+ var labelText = self.formatDate(data[di].date)
|
|
|
+ ctx.fillText(labelText, labelX, padding.top + plotH + 18 * dpr)
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.draw()
|
|
|
+ }).exec()
|
|
|
+ })
|
|
|
+ },
|
|
|
+
|
|
|
+ drawScatter() {
|
|
|
+ if (this.scatterData.length < 3) return
|
|
|
+
|
|
|
+ var canvasId = 'scatterCanvas'
|
|
|
+ var query = uni.createSelectorQuery()
|
|
|
+ var self = this
|
|
|
+
|
|
|
+ query.select('#' + canvasId).boundingClientRect(function(rect) {
|
|
|
+ if (!rect) return
|
|
|
+ var ctx = uni.createCanvasContext(canvasId, self)
|
|
|
+ var dpr = 2
|
|
|
+ var width = rect.width || 300
|
|
|
+ var height = rect.height || 200
|
|
|
+ var canvasWidth = width * dpr
|
|
|
+ var canvasHeight = height * dpr
|
|
|
+
|
|
|
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight)
|
|
|
+
|
|
|
+ var padding = { top: 20 * dpr, right: 20 * dpr, bottom: 30 * dpr, left: 30 * dpr }
|
|
|
+ var plotW = canvasWidth - padding.left - padding.right
|
|
|
+ var plotH = canvasHeight - padding.top - padding.bottom
|
|
|
+
|
|
|
+ // Draw background with 4 quadrants
|
|
|
+ // Q1 (high arousal, high valence): top-right
|
|
|
+ var midX = padding.left + plotW / 2
|
|
|
+ var midY = padding.top + plotH / 2
|
|
|
+
|
|
|
+ // Quadrant backgrounds
|
|
|
+ ctx.setFillStyle('rgba(255,240,240,0.3)')
|
|
|
+ ctx.fillRect(midX, padding.top, plotW / 2, plotH / 2) // high arousal, high valence
|
|
|
+ ctx.setFillStyle('rgba(240,240,255,0.3)')
|
|
|
+ ctx.fillRect(padding.left, padding.top, plotW / 2, plotH / 2) // high arousal, low valence
|
|
|
+ ctx.setFillStyle('rgba(255,248,240,0.3)')
|
|
|
+ ctx.fillRect(midX, midY, plotW / 2, plotH / 2) // low arousal, high valence
|
|
|
+ ctx.setFillStyle('rgba(245,245,245,0.3)')
|
|
|
+ ctx.fillRect(padding.left, midY, plotW / 2, plotH / 2) // low arousal, low valence
|
|
|
+
|
|
|
+ // Quadrant labels
|
|
|
+ ctx.setFontSize(10 * dpr)
|
|
|
+ ctx.setFillStyle('rgba(0,0,0,0.15)')
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.fillText('兴奋', padding.left + plotW * 0.75, padding.top + plotH * 0.3)
|
|
|
+ ctx.fillText('烦躁', padding.left + plotW * 0.25, padding.top + plotH * 0.3)
|
|
|
+ ctx.fillText('平静', padding.left + plotW * 0.75, padding.top + plotH * 0.8)
|
|
|
+ ctx.fillText('低落', padding.left + plotW * 0.25, padding.top + plotH * 0.8)
|
|
|
+
|
|
|
+ // Axes
|
|
|
+ ctx.setStrokeStyle('#E0E0E0')
|
|
|
+ ctx.setLineWidth(1 * dpr)
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(padding.left, padding.top + plotH / 2)
|
|
|
+ ctx.lineTo(padding.left + plotW, padding.top + plotH / 2)
|
|
|
+ ctx.stroke()
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.moveTo(padding.left + plotW / 2, padding.top)
|
|
|
+ ctx.lineTo(padding.left + plotW / 2, padding.top + plotH)
|
|
|
+ ctx.stroke()
|
|
|
+
|
|
|
+ // Axis labels
|
|
|
+ ctx.setFontSize(9 * dpr)
|
|
|
+ ctx.setFillStyle('#999')
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.fillText('效价 →', padding.left + plotW / 2, padding.top + plotH + 18 * dpr)
|
|
|
+ ctx.save()
|
|
|
+ ctx.translate(padding.left - 4 * dpr, padding.top + plotH / 2)
|
|
|
+ ctx.rotate(-Math.PI / 2)
|
|
|
+ ctx.setTextAlign('center')
|
|
|
+ ctx.fillText('激活度', 0, 0)
|
|
|
+ ctx.restore()
|
|
|
+
|
|
|
+ // Draw scatter points
|
|
|
+ for (var i = 0; i < self.scatterData.length; i++) {
|
|
|
+ var pt = self.scatterData[i]
|
|
|
+ var px = padding.left + (pt.valence / 10) * plotW
|
|
|
+ var py = padding.top + plotH - (pt.arousal / 10) * plotH
|
|
|
+
|
|
|
+ // Color based on valence
|
|
|
+ var color
|
|
|
+ if (pt.valence >= 7) {
|
|
|
+ color = '#10B981'
|
|
|
+ } else if (pt.valence >= 5) {
|
|
|
+ color = '#FFD93D'
|
|
|
+ } else {
|
|
|
+ color = '#FF6B9D'
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.beginPath()
|
|
|
+ ctx.arc(px, py, 5 * dpr, 0, 2 * Math.PI)
|
|
|
+ ctx.setFillStyle(color)
|
|
|
+ ctx.setGlobalAlpha(0.7)
|
|
|
+ ctx.fill()
|
|
|
+ ctx.setGlobalAlpha(1)
|
|
|
+ }
|
|
|
+
|
|
|
+ ctx.draw()
|
|
|
+ }).exec()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -282,6 +601,38 @@ export default {
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
+/* ===== 图表卡片 ===== */
|
|
|
+.chart-card {
|
|
|
+ margin: 24rpx 32rpx;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ padding: 28rpx;
|
|
|
+ box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.chart-card .card-title {
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #333;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 4rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-card .card-subtitle {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #999;
|
|
|
+ display: block;
|
|
|
+ margin-bottom: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.line-chart-canvas,
|
|
|
+.scatter-canvas {
|
|
|
+ width: 100%;
|
|
|
+ height: 360rpx;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ background: #FCFCFC;
|
|
|
+}
|
|
|
+
|
|
|
.calendar-card,
|
|
|
.distribution-card,
|
|
|
.daily-list-card {
|
|
|
@@ -322,29 +673,61 @@ export default {
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
+ border-radius: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.calendar-day.has-data {
|
|
|
+ cursor: pointer;
|
|
|
}
|
|
|
|
|
|
.is-today {
|
|
|
- background: #FFF0F5;
|
|
|
- border-radius: 12rpx;
|
|
|
+ border: 2rpx solid #FF6B9D;
|
|
|
+ box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
.day-num {
|
|
|
- font-size: 26rpx;
|
|
|
+ font-size: 24rpx;
|
|
|
color: #333;
|
|
|
}
|
|
|
|
|
|
-.day-emoji {
|
|
|
- font-size: 24rpx;
|
|
|
- margin-top: 4rpx;
|
|
|
+.day-emoji-small {
|
|
|
+ font-size: 20rpx;
|
|
|
+ margin-top: 2rpx;
|
|
|
}
|
|
|
|
|
|
-.weather-sunny { background: #FFF8E1; border-radius: 8rpx; }
|
|
|
-.weather-cloudy { background: #F5F5F5; border-radius: 8rpx; }
|
|
|
-.weather-rainy { background: #E3F2FD; border-radius: 8rpx; }
|
|
|
-.weather-stormy { background: #EDE7F6; border-radius: 8rpx; }
|
|
|
-.weather-rainbow { background: #FCE4EC; border-radius: 8rpx; }
|
|
|
+.day-score {
|
|
|
+ font-size: 18rpx;
|
|
|
+ color: #888;
|
|
|
+ margin-top: 2rpx;
|
|
|
+}
|
|
|
|
|
|
+/* 热力图例 */
|
|
|
+.heatmap-legend {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 12rpx;
|
|
|
+ margin-top: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-label {
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: #999;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-bar {
|
|
|
+ display: flex;
|
|
|
+ gap: 2rpx;
|
|
|
+ border-radius: 6rpx;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.legend-step {
|
|
|
+ width: 24rpx;
|
|
|
+ height: 16rpx;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 情绪分布 ===== */
|
|
|
.distribution-list {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
@@ -390,6 +773,7 @@ export default {
|
|
|
text-align: right;
|
|
|
}
|
|
|
|
|
|
+/* ===== 每日记录 ===== */
|
|
|
.daily-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
@@ -402,17 +786,40 @@ export default {
|
|
|
font-size: 24rpx;
|
|
|
color: #999;
|
|
|
width: 100rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.daily-emoji {
|
|
|
font-size: 32rpx;
|
|
|
+ width: 40rpx;
|
|
|
+ text-align: center;
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.daily-weather {
|
|
|
font-size: 26rpx;
|
|
|
color: #666;
|
|
|
+ width: 120rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.daily-tags {
|
|
|
+ display: flex;
|
|
|
+ gap: 8rpx;
|
|
|
+ flex-wrap: wrap;
|
|
|
}
|
|
|
|
|
|
+.daily-tag {
|
|
|
+ font-size: 20rpx;
|
|
|
+ padding: 4rpx 12rpx;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+.score-tag { background: #FF6B9D; }
|
|
|
+.type-tag { background: #8B5CF6; }
|
|
|
+.arousal-tag { background: #3B82F6; }
|
|
|
+
|
|
|
.empty-state {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|