| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085 |
- <template>
- <!-- 家庭天盘详情页 -->
- <view class="family-dashboard">
- <!-- PDF 导出用隐藏 canvas -->
- <canvas canvas-id="weeklyReportCanvas" id="weeklyReportCanvas" style="width: 1px; height: 1px; position: absolute; top: -1000rpx;"></canvas>
- <!-- 头部导航 -->
- <view class="dashboard-header">
- <view class="dashboard-icon" @click="goBack">
- <text class="dashboard-icon-text">←</text>
- </view>
- <text class="dashboard-title">家庭天盘</text>
- <view class="dashboard-menu" @click="showMenu = !showMenu">
- <text class="dashboard-menu-text">☰</text>
- <view class="dashboard-menu-dropdown" v-if="showMenu">
- <view class="menu-item" @click="onMenuItemClick('week')">本周运势</view>
- <view class="menu-item" @click="onMenuItemClick('month')">本月趋势</view>
- <view class="menu-item" @click="onMenuItemClick('history')">历史数据</view>
- <view class="menu-item" @click="onMenuItemClick('export')">导出周报</view>
- </view>
- </view>
- </view>
-
- <!-- 数据概览卡片 -->
- <view class="dashboard-card" :style="cardStyle">
- <view class="card-header">
- <text class="card-title">{{ currentDateLabel }}</text>
- <text class="card-subtitle">五行能量动态</text>
- </view>
- <view class="card-body">
- <!-- 运势数据展示 -->
- <view class="fortune-data">
- <!-- 主导五行 -->
- <view class="data-item">
- <view class="data-icon" :style="{ backgroundColor: elementColors[familyFortune.dominantElement] }">
- <text class="data-icon-text">🌱</text>
- </view>
- <view class="data-info">
- <text class="data-label">主导五行</text>
- <text class="data-value">{{ getElementName(familyFortune.dominantElement) }}</text>
- </view>
- </view>
-
- <!-- 吉位 -->
- <view class="data-item">
- <view class="data-icon" :style="{ backgroundColor: '#FFA500' }">
- <text class="data-icon-text">🧭</text>
- </view>
- <view class="data-info">
- <text class="data-label">今日吉位</text>
- <text class="data-value">{{ familyFortune.luckyDirection }}方</text>
- </view>
- </view>
-
- <!-- 运势 -->
- <view class="data-item">
- <view class="data-icon" :style="{ backgroundColor: getFortuneColor(familyFortune.fortuneLevel) }">
- <text class="data-icon-text">⭐</text>
- </view>
- <view class="data-info">
- <text class="data-label">今日运势</text>
- <text class="data-value">{{ familyFortune.fortuneLevelText }}</text>
- </view>
- </view>
- </view>
-
- <!-- 心情进度条 -->
- <view class="mood-section">
- <text class="mood-emoji">{{ familyFortune.moodEmoji }}</text>
- <text class="mood-text">{{ familyFortune.moodText }}</text>
- <view class="mood-bar">
- <view class="mood-bar-fill" :style="{ width: familyFortune.moodScore + '%' }"></view>
- </view>
- </view>
-
- <!-- 五行罗盘 -->
- <view class="compass-section">
- <canvas id="dashboardCompass" type="2d" class="dashboard-compass"></canvas>
- </view>
- </view>
- <view class="card-footer">
- <text class="footer-tip">✨ {{ familyFortune.dailyTip }}</text>
- </view>
- </view>
-
- <!-- 周数据趋势图 -->
- <view class="dashboard-section" v-if="viewMode === 'week'">
- <view class="section-header">
- <text class="section-title">本周能量趋势</text>
- </view>
- <view class="section-body">
- <view class="trend-chart">
- <canvas id="weekTrendChart" type="2d" class="trend-canvas"></canvas>
- </view>
- </view>
- </view>
-
- <!-- 月数据趋势图 -->
- <view class="dashboard-section" v-if="viewMode === 'month'">
- <view class="section-header">
- <text class="section-title">本月运势走势</text>
- </view>
- <view class="section-body">
- <view class="month-trend">
- <view class="trend-calendar">
- <view class="calendar-header">
- <text class="calendar-month">{{ currentMonthLabel }}</text>
- </view>
- <view class="calendar-grid">
- <view class="calendar-day" v-for="(day, index) in monthDays" :key="index" :class="day.dayClass">
- <text class="day-number">{{ day.day }}</text>
- <view class="day-fortune" v-if="day.fortune">
- <view class="fortune-bubble" :class="'fortune-lv-' + getFortuneLevel(day.fortune)"></view>
- </view>
- </view>
- </view>
- </view>
- </view>
- </view>
- </view>
-
- <!-- 历史周报列表 -->
- <view class="dashboard-section" v-if="viewMode === 'history'">
- <view class="section-header">
- <text class="section-title">历史周报</text>
- </view>
- <view class="section-body">
- <view class="report-list">
- <view class="report-item" v-for="(report, index) in weeklyReports" :key="index" @click="viewWeeklyReport(report)">
- <view class="report-icon">📄</view>
- <view class="report-info">
- <text class="report-title">第 {{ report.weekNumber }} 周报告</text>
- <text class="report-date">{{ report.startDate }} ~ {{ report.endDate }}</text>
- </view>
- <view class="report-action">
- <text class="report-download" @click.stop="downloadReport(report)">下载</text>
- </view>
- </view>
- </view>
- </view>
- </view>
-
- <!-- 周报详情模态框 -->
- <view class="modal-overlay" v-if="showWeeklyReport" @click="showWeeklyReport = false">
- <view class="weekly-report-modal" @click.stop>
- <view class="modal-header">
- <text class="modal-title">第 {{ currentWeeklyReport.weekNumber }} 周报告</text>
- <view class="modal-close" @click="showWeeklyReport = false">✕</view>
- </view>
- <view class="modal-body">
- <scroll-view scroll-y="true" class="modal-content">
- <mp-html :content="currentWeeklyReport.content" />
- </scroll-view>
- </view>
- <view class="modal-footer">
- <button class="modal-btn" @click="downloadReport(currentWeeklyReport)">下载PDF</button>
- </view>
- </view>
- </view>
- </view>
- </template>
- <script>
- import MpHtml from '@/components/mp-html/mp-html.vue'
- import { parseDate } from '../../utils/format.js'
- /**
- * FamilyDashboard — 家庭天盘详情页
- * 包含:运势数据、能量趋势、历史周报、月度日历等
- */
- export default {
- name: 'FamilyDashboard',
- components: { MpHtml },
- data() {
- return {
- showMenu: false,
- viewMode: 'week', // week, month, history
- familyFortune: {
- dominantElement: '木',
- luckyDirection: '东',
- fortuneLevel: '平',
- fortuneLevelText: '平',
- fortuneDescription: '今日宜静心养性,保持平和心态',
- moodScore: 65,
- moodEmoji: '😊',
- moodText: '平静',
- dailyTip: '保持心情平静,避免过度思虑',
- weeklyTip: '本周建议:聚焦个人成长,适当社交'
- },
- currentDate: new Date(),
- weekFortunes: [],
- monthFortunes: [],
- weeklyReports: [],
- elementColors: {
- '木': '#10B981',
- '火': '#FF6B9D',
- '土': '#FF8C42',
- '金': '#6366F1',
- '水': '#3B82F6'
- },
- // 详情模态框
- showWeeklyReport: false,
- currentWeeklyReport: null,
- // Canvas 对象
- compassCtx: null,
- weekChartCtx: null
- }
- },
- computed: {
- cardStyle() {
- return {
- backgroundColor: this.elementColors[this.familyFortune.dominantElement] + '10'
- }
- },
- currentDateLabel() {
- const week = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
- return `${this.familyFortune.luckyDirection}方 · ${week[this.currentDate.getDay()]} ${this.formatDate(this.currentDate)}`
- },
- currentMonthLabel() {
- return `${this.currentDate.getFullYear()}年${this.currentDate.getMonth() + 1}月`
- },
- monthDays() {
- const year = this.currentDate.getFullYear()
- const month = this.currentDate.getMonth()
- const days = []
-
- // 获取当月第一天是星期几
- const firstDay = new Date(year, month, 1).getDay()
- // 获取当月总天数
- const lastDate = new Date(year, month + 1, 0).getDate()
-
- // 填充上月尾部
- for (let i = 0; i < firstDay; i++) {
- const emptyDay = { day: '', date: null }
- emptyDay.dayClass = this.getDayClass(emptyDay).join(' ')
- days.push(emptyDay)
- }
-
- // 填充当月
- for (let i = 1; i <= lastDate; i++) {
- const date = new Date(year, month, i)
- const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(i).padStart(2, '0')}`
- const dayData = {
- day: i,
- date: date,
- fortune: null
- }
- // 查找对应的运势数据
- const fortune = this.monthFortunes.find(f =>
- f.fortuneDate === dateStr
- )
- if (fortune) {
- dayData.fortune = fortune
- }
- // 预计算 class 字符串(小程序 :class 不支持方法调用)
- dayData.dayClass = this.getDayClass(dayData).join(' ')
- days.push(dayData)
- }
-
- return days
- }
- },
- onLoad() {
- // 初始化页面
- this.loadTodayFortune()
- this.loadWeekFortuneData()
- this.loadMonthlyFortuneData()
- this.loadWeeklyReports()
-
- // 初始化 Canvas
- this.$nextTick(() => {
- this.initCompassCanvas()
- this.initWeekChartCanvas()
- })
- },
- methods: {
- // 加载今日运势数据
- loadTodayFortune() {
- // TODO: 对接后端 /api/mind/fortune 接口
- // 这里使用默认占位数据
- this.familyFortune = {
- dominantElement: '火',
- luckyDirection: '南',
- fortuneLevel: '大吉',
- fortuneLevelText: '大吉',
- fortuneDescription: '今日热情洋溢,宜沟通表达',
- moodScore: 85,
- moodEmoji: '😄',
- moodText: '喜悦',
- dailyTip: '把握今日良机,多与家人互动',
- weeklyTip: '本周建议:多陪伴家人,适度运动'
- }
- },
-
- // 加载本周运势数据
- loadWeekFortuneData() {
- // TODO: 对接后端 /api/mind/fortune/week 接口
- const mockData = []
- const today = new Date()
- for (let i = 0; i < 7; i++) {
- const date = new Date(today)
- date.setDate(date.getDate() - (today.getDay() - i))
-
- const levels = ['大吉', '吉', '平', '凶', '大凶']
- const levelIndex = Math.floor(Math.random() * 5)
- const elements = ['木', '火', '土', '金', '水']
-
- mockData.push({
- date: this.formatDate(date),
- fortuneLevel: levels[levelIndex],
- fortuneLevelText: levels[levelIndex],
- fortuneScore: 50 + (levelIndex < 2 ? (3 - levelIndex) * 15 : (levelIndex > 2 ? (levelIndex - 2) * -20 : 0)),
- dominantElement: elements[Math.floor(Math.random() * 5)]
- })
- }
- this.weekFortunes = mockData
- },
-
- // 加载本月运势数据
- loadMonthlyFortuneData() {
- // TODO: 对接后端 /api/mind/fortune/month 接口
- const mockData = []
- const today = new Date()
- const daysInMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0).getDate()
-
- for (let i = 1; i <= daysInMonth; i++) {
- const date = new Date(today.getFullYear(), today.getMonth(), i)
- const dateStr = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(i).padStart(2, '0')}`
-
- const levels = ['大吉', '吉', '平', '凶', '大凶']
- const levelIndex = Math.floor(Math.random() * 5)
-
- mockData.push({
- fortuneDate: dateStr,
- fortuneLevel: levels[levelIndex],
- fortuneLevelText: levels[levelIndex],
- luckyDirection: ['东', '南', '西', '北', '中'][Math.floor(Math.random() * 5)] + '方',
- dominantElement: ['木', '火', '土', '金', '水'][Math.floor(Math.random() * 5)]
- })
- }
- this.monthFortunes = mockData
- },
-
- // 加载历史周报
- loadWeeklyReports() {
- // TODO: 对接后端 /api/mind/fortune/reports 接口
- const mockReports = []
- for (let i = 1; i <= 8; i++) {
- mockReports.push({
- weekNumber: i,
- startDate: `2026-06-${String(28 - i * 7).padStart(2, '0')}`,
- endDate: `2026-07-${String(4 - i).padStart(2, '0')}`,
- content: this.generateMockReportContent(i)
- })
- }
- this.weeklyReports = mockReports
- },
-
- // 生成模拟周报内容
- generateMockReportContent(weekNumber) {
- const elements = ['木', '火', '土', '金', '水']
- const element = elements[weekNumber % 5]
- return `<div style="font-family: sans-serif; line-height: 1.6; color: #333;">
- <h2 style="color: #FF6B9D; text-align: center;">家庭运势周报 - 第${weekNumber}周</h2>
- <p style="text-indent: 2em;">本周主导五行:<span style="color: ${this.elementColors[element]};">${element}</span>,
- 意味着家庭整体运势偏向${{ 木: '成长与和谐', 火: '热情与活力', 土: '稳定与健康', 金: '收获与进步', 水: '智慧与灵活' }[element]}。</p>
- <h3 style="color: #666;">运势概况</h3>
- <p style="text-indent: 2em;">本周吉位平均位于 <strong>${['东', '南', '西', '北'][weekNumber % 4]}方</strong>,建议多在此方位进行重要活动。
- 运势整体呈现先抑后扬之势,周二、周四较为低迷,周五后逐步好转。</p>
- <h3 style="color: #666;">心理状态</h3>
- <p style="text-indent: 2em;">家庭成员情绪指数平均${70 + weekNumber}分,整体状态${weekNumber > 4 ? '良好' : '一般'}。
- ${weekNumber > 4 ? '建议保持现有和谐氛围' : '需注意情绪调节,避免矛盾升级'}。</p>
- <h3 style="color: #666;">宜忌提示</h3>
- <p style="text-indent: 2em;"><strong>宜:</strong>${{ 木: '植树种花、交友聚会', 火: '聚餐联谊、旅行观光', 土: '健康管理、家居整理', 金: '投资理财、学习进修', 水: '阅读思考、社交活动' }[element]}</p>
- <p style="text-indent: 2em;margin-top: -10px;"><strong>忌:</strong>${{ 木: '冲动举动、过度开支', 火: '过度消耗、睡眠不足', 土: '情绪压抑、忽视健康', 金: '过于拘谨、忽视情感', 水: '沉迷游戏、忽略家人' }[element]}</p>
- </div>`
- },
-
- // 初始化罗盘 Canvas
- initCompassCanvas() {
- const query = uni.createSelectorQuery().in(this)
- query.select('#dashboardCompass')
- .fields({ node: true, size: true })
- .exec((res) => {
- if (!res || !res[0] || !res[0].node) return
-
- const canvas = res[0].node
- const dpr = uni.getWindowInfo().pixelRatio || 2
- const w = res[0].width
- const h = res[0].height
-
- canvas.width = w * dpr
- canvas.height = h * dpr
- const ctx = canvas.getContext('2d')
- ctx.scale(dpr, dpr)
-
- this.compassCtx = ctx
- this.drawCompass(ctx, w, h)
- })
- },
-
- // 绘制罗盘
- drawCompass(ctx, w, h) {
- const cx = w / 2
- const cy = h / 2
- const outerR = Math.min(cx, cy) - 4
-
- // 清空
- ctx.clearRect(0, 0, w, h)
-
- // 五行方位
- const directions = [
- { label: '北', element: '水', angle: -Math.PI / 2 },
- { label: '东', element: '木', angle: 0 },
- { label: '南', element: '火', angle: Math.PI / 2 },
- { label: '西', element: '金', angle: Math.PI },
- { label: '中', element: '土', angle: null }
- ]
-
- // 吉位方向
- const luckyDir = this.familyFortune.luckyDirection
- const luckyColor = this.elementColors[this.familyFortune.dominantElement]
-
- // 外圈背景
- ctx.beginPath()
- ctx.arc(cx, cy, outerR, 0, Math.PI * 2)
- ctx.fillStyle = '#FFF5F7'
- ctx.fill()
- ctx.strokeStyle = luckyColor + '40'
- ctx.lineWidth = 1.5
- ctx.stroke()
-
- // 绘制四个方位
- for (let i = 0; i < 4; i++) {
- const d = directions[i]
- const color = this.elementColors[d.element]
-
- // 扇区线
- ctx.beginPath()
- ctx.moveTo(cx, cy)
- const ex = cx + outerR * Math.cos(d.angle)
- const ey = cy + outerR * Math.sin(d.angle)
- ctx.lineTo(ex, ey)
- ctx.strokeStyle = color + '30'
- ctx.lineWidth = 1
- ctx.stroke()
-
- // 方位标签
- const labelR = outerR * 0.7
- const lx = cx + labelR * Math.cos(d.angle)
- const ly = cy + labelR * Math.sin(d.angle)
-
- const isLucky = d.label === luckyDir
- if (isLucky) {
- // 吉位高亮圆
- ctx.beginPath()
- ctx.arc(lx, ly, 12, 0, Math.PI * 2)
- ctx.fillStyle = luckyColor + '20'
- ctx.fill()
- ctx.strokeStyle = luckyColor
- ctx.lineWidth = 1.5
- ctx.stroke()
- }
-
- ctx.fillStyle = isLucky ? luckyColor : '#9CA3AF'
- ctx.font = (isLucky ? 'bold ' : '') + '12px sans-serif'
- ctx.textAlign = 'center'
- ctx.textBaseline = 'middle'
- ctx.fillText(d.label, lx, ly)
- }
-
- // 中心点
- ctx.beginPath()
- ctx.arc(cx, cy, 5, 0, Math.PI * 2)
- ctx.fillStyle = luckyColor
- ctx.fill()
- ctx.strokeStyle = '#FFFFFF'
- ctx.lineWidth = 2
- ctx.stroke()
- },
-
- // 初始化周趋势图 Canvas
- initWeekChartCanvas() {
- const query = uni.createSelectorQuery().in(this)
- query.select('#weekTrendChart')
- .fields({ node: true, size: true })
- .exec((res) => {
- if (!res || !res[0] || !res[0].node) return
-
- const canvas = res[0].node
- const dpr = uni.getWindowInfo().pixelRatio || 2
- const w = res[0].width
- const h = res[0].height
-
- canvas.width = w * dpr
- canvas.height = h * dpr
- const ctx = canvas.getContext('2d')
- ctx.scale(dpr, dpr)
-
- this.weekChartCtx = ctx
- this.drawWeekTrendChart(ctx, w, h)
- })
- },
-
- // 绘制周趋势图
- drawWeekTrendChart(ctx, w, h) {
- const padding = 40
- const chartW = w - padding * 2
- const chartH = h - padding * 2
-
- // 清空
- ctx.clearRect(0, 0, w, h)
-
- // 背景
- ctx.fillStyle = '#FFFFFF'
- ctx.fillRect(padding, padding, chartW, chartH)
- ctx.strokeStyle = '#F3F4F6'
- ctx.lineWidth = 1
- ctx.strokeRect(padding, padding, chartW, chartH)
-
- if (this.weekFortunes.length === 0) return
-
- // 坐标系
- const maxScore = 100
- const minScore = 0
- const step = 20
- const scoreSteps = Math.ceil((maxScore - minScore) / step)
-
- // Y 轴刻度
- ctx.fillStyle = '#6B7280'
- ctx.font = '10px sans-serif'
- ctx.textAlign = 'right'
- ctx.textBaseline = 'middle'
-
- for (let i = 0; i <= scoreSteps; i++) {
- const y = padding + chartH - (i * chartH / scoreSteps)
- const score = minScore + i * step
- ctx.fillText(score, padding - 10, y)
-
- ctx.beginPath()
- ctx.moveTo(padding - 5, y)
- ctx.lineTo(padding, y)
- ctx.strokeStyle = '#E5E7EB'
- ctx.stroke()
- }
-
- // X 轴刻度
- const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
- const labels = this.weekFortunes.map((f, i) => weekDays[i] || '')
-
- ctx.fillStyle = '#6B7280'
- ctx.font = '10px sans-serif'
- ctx.textAlign = 'center'
- ctx.textBaseline = 'top'
-
- for (let i = 0; i < labels.length; i++) {
- const x = padding + i * (chartW / (labels.length - 1))
- ctx.fillText(labels[i], x, padding + chartH + 10)
-
- ctx.beginPath()
- ctx.moveTo(x, padding + chartH)
- ctx.lineTo(x, padding + chartH + 5)
- ctx.strokeStyle = '#E5E7EB'
- ctx.stroke()
- }
-
- // 绘制折线
- ctx.beginPath()
- for (let i = 0; i < this.weekFortunes.length; i++) {
- const f = this.weekFortunes[i]
- const x = padding + i * (chartW / (this.weekFortunes.length - 1))
- const y = padding + chartH - (f.fortuneScore - minScore) * chartH / (maxScore - minScore)
-
- if (i === 0) {
- ctx.moveTo(x, y)
- } else {
- ctx.lineTo(x, y)
- }
-
- // 数据点
- ctx.beginPath()
- ctx.arc(x, y, 4, 0, Math.PI * 2)
- ctx.fillStyle = this.getFortuneColor(f.fortuneLevel)
- ctx.fill()
- ctx.strokeStyle = '#FFFFFF'
- ctx.lineWidth = 2
- ctx.stroke()
-
- // 恢复路径
- ctx.beginPath()
- ctx.moveTo(x, y)
- if (i < this.weekFortunes.length - 1) {
- const nextF = this.weekFortunes[i + 1]
- const nextX = padding + (i + 1) * (chartW / (this.weekFortunes.length - 1))
- const nextY = padding + chartH - (nextF.fortuneScore - minScore) * chartH / (maxScore - minScore)
- ctx.lineTo(nextX, nextY)
- }
- }
-
- ctx.strokeStyle = this.elementColors[this.familyFortune.dominantElement]
- ctx.lineWidth = 2
- ctx.stroke()
- },
-
- // 格式化日期
- formatDate(date) {
- if (!date) return ''
- const d = parseDate(date)
- if (!d) return ''
- return `${d.getMonth() + 1}月${d.getDate()}日`
- },
-
- // 菜单点击处理
- onMenuItemClick(mode) {
- this.showMenu = false
- if (mode === 'export') {
- uni.showToast({ title: '周报生成中...', icon: 'loading' })
- this.exportWeeklyReport()
- return
- }
- this.viewMode = mode
- },
-
- // 返回上一页
- goBack() {
- uni.navigateBack()
- },
-
- // 获取五行名称
- getElementName(element) {
- const names = { 木: '木', 火: '火', 土: '土', 金: '金', 水: '水' }
- return names[element] || element
- },
-
- // 获取运势颜色
- getFortuneColor(level) {
- const colors = {
- '大吉': '#EF4444',
- '吉': '#F97316',
- '平': '#FBBF24',
- '凶': '#6366F1',
- '大凶': '#8B5CF6'
- }
- return colors[level] || '#666666'
- },
-
- // 获取运势等级数值(用于CSS class)
- getFortuneLevel(dayFortune) {
- if (!dayFortune) return 2
- var levelMap = { '大吉': 0, '吉': 1, '平': 2, '凶': 3, '大凶': 4 }
- var level = dayFortune.fortuneLevel || dayFortune.fortuneLevelText || ''
- return levelMap[level] !== undefined ? levelMap[level] : 2
- },
- // 获取日历日样式
- getDayClass(day) {
- const classes = []
- if (!day.date) {
- classes.push('empty-day')
- return classes
- }
-
- if (day.day === new Date().getDate() &&
- day.date.getMonth() === new Date().getMonth() &&
- day.date.getFullYear() === new Date().getFullYear()) {
- classes.push('current-day')
- }
-
- if (day.fortune) {
- classes.push('has-data')
- classes.push('fortune-lv-' + this.getFortuneLevel(day.fortune))
- }
-
- return classes
- },
-
- // 查看周报
- viewWeeklyReport(report) {
- this.currentWeeklyReport = report
- this.showWeeklyReport = true
- },
-
- // 下载周报
- downloadReport(report) {
- uni.showToast({ title: '周报生成中...', icon: 'loading' })
- // TODO: 对接后端生成PDF接口
- setTimeout(() => {
- uni.showToast({ title: '周报已生成,请在文件管理中查看', icon: 'none' })
- }, 2000)
- },
-
- // 导出本周周报
- exportWeeklyReport() {
- uni.showToast({ title: '正在生成本周周报', icon: 'loading' })
- // TODO: 将画布数据发送到后端生成PDF
- setTimeout(() => {
- uni.showToast({ title: '周报已生成,请在历史周报中下载', icon: 'none' })
- }, 3000)
- }
- }
- }
- </script>
- <style scoped>
- .family-dashboard {
- background-color: #F9FAFB;
- min-height: 100vh;
- padding-bottom: 40rpx;
- }
- /* ===== 头部导航 ===== */
- .dashboard-header {
- position: sticky;
- top: 0;
- z-index: 100;
- background-color: #FFFFFF;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- padding: 30rpx 30rpx 16rpx;
- border-bottom: 1rpx solid #F3F4F6;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
- }
- .dashboard-icon {
- width: 64rpx;
- height: 64rpx;
- border-radius: 32rpx;
- background-color: #FFF0F3;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .dashboard-icon-text {
- font-size: 32rpx;
- color: #FF6B9D;
- }
- .dashboard-title {
- font-size: 32rpx;
- font-weight: 700;
- color: #1E293B;
- }
- .dashboard-menu {
- position: relative;
- }
- .dashboard-menu-text {
- font-size: 36rpx;
- color: #9CA3AF;
- }
- .dashboard-menu-dropdown {
- position: absolute;
- right: 0;
- top: 60rpx;
- background: #FFFFFF;
- border-radius: 12rpx;
- box-shadow: 0 8rpx 16rpx rgba(0, 0, 0, 0.1);
- padding: 12rpx 0;
- min-width: 200rpx;
- z-index: 10;
- }
- .menu-item {
- padding: 20rpx 24rpx;
- font-size: 28rpx;
- color: #374151;
- }
- .menu-item:active {
- background-color: #F9FAFB;
- }
- /* ===== 数据卡片 ===== */
- .dashboard-card {
- margin: 20rpx 30rpx;
- border-radius: 24rpx;
- overflow: hidden;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
- }
- .card-header {
- padding: 24rpx 30rpx 16rpx;
- border-bottom: 1rpx solid rgba(255, 255, 255, 0.2);
- }
- .card-title {
- font-size: 28rpx;
- font-weight: 600;
- color: #1E293B;
- }
- .card-subtitle {
- font-size: 22rpx;
- color: #64748B;
- margin-top: 4rpx;
- }
- .card-body {
- padding: 24rpx;
- }
- .card-footer {
- background-color: rgba(255, 255, 255, 0.8);
- padding: 16rpx 30rpx;
- border-top: 1rpx solid rgba(0, 0, 0, 0.05);
- }
- .footer-tip {
- font-size: 24rpx;
- color: #6B7280;
- }
- /* 运势数据项 */
- .fortune-data {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- margin-bottom: 24rpx;
- }
- .data-item {
- width: 50%;
- display: flex;
- flex-direction: row;
- align-items: center;
- margin-bottom: 16rpx;
- }
- .data-icon {
- width: 56rpx;
- height: 56rpx;
- border-radius: 16rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 16rpx;
- }
- .data-icon-text {
- font-size: 28rpx;
- }
- .data-info {
- display: flex;
- flex-direction: column;
- }
- .data-label {
- font-size: 22rpx;
- color: #64748B;
- margin-bottom: 4rpx;
- }
- .data-value {
- font-size: 26rpx;
- font-weight: 600;
- color: #1E293B;
- }
- /* 心情部分 */
- .mood-section {
- margin: 24rpx 0;
- }
- .mood-emoji {
- font-size: 52rpx;
- margin-right: 16rpx;
- }
- .mood-text {
- font-size: 28rpx;
- font-weight: 600;
- color: #1E293B;
- vertical-align: bottom;
- }
- .mood-bar {
- margin-top: 16rpx;
- height: 12rpx;
- background-color: #E5E7EB;
- border-radius: 6rpx;
- overflow: hidden;
- }
- .mood-bar-fill {
- height: 100%;
- background: linear-gradient(90deg, #FF6B9D, #FF8FB1);
- border-radius: 6rpx;
- transition: width 0.5s;
- }
- /* 五行罗盘 */
- .compass-section {
- text-align: center;
- margin: 24rpx 0;
- }
- .dashboard-compass {
- width: 300rpx;
- height: 300rpx;
- margin: 0 auto;
- }
- /* ===== 数据趋势区块 ===== */
- .dashboard-section {
- margin: 20rpx 30rpx;
- background-color: #FFFFFF;
- border-radius: 24rpx;
- overflow: hidden;
- box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
- }
- .section-header {
- padding: 20rpx 30rpx;
- border-bottom: 1rpx solid #F3F4F6;
- }
- .section-title {
- font-size: 28rpx;
- font-weight: 600;
- color: #1E293B;
- }
- .section-body {
- padding: 20rpx 30rpx 30rpx;
- }
- /* 周趋势图 */
- .trend-chart {
- height: 400rpx;
- }
- .trend-canvas {
- width: 100%;
- height: 400rpx;
- }
- /* 月趋势 - 日历 */
- .trend-calendar {
- font-size: 24rpx;
- }
- .calendar-header {
- text-align: center;
- margin-bottom: 20rpx;
- }
- .calendar-month {
- font-size: 28rpx;
- font-weight: 600;
- color: #1E293B;
- }
- .calendar-grid {
- display: grid;
- grid-template-columns: repeat(7, 1fr);
- gap: 8rpx;
- }
- .calendar-day {
- aspect-ratio: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- border-radius: 8rpx;
- position: relative;
- }
- .calendar-day.empty-day {
- background-color: transparent;
- cursor: default;
- }
- .calendar-day.has-data {
- background-color: #F9FAFB;
- }
- .calendar-day.current-day {
- background-color: #FFF0F3;
- border: 1rpx solid #FF6B9D;
- }
- .day-number {
- font-size: 24rpx;
- }
- .day-fortune {
- width: 100%;
- height: 36rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .fortune-bubble {
- width: 16rpx;
- height: 16rpx;
- border-radius: 50%;
- }
- .fortune-lv-0, .fortune-lv-1 { background-color: #EF4444; }
- .fortune-lv-2 { background-color: #FBBF24; }
- .fortune-lv-3, .fortune-lv-4 { background-color: #8B5CF6; }
- /* ===== 历史周报 ===== */
- .report-list {
- display: flex;
- flex-direction: column;
- gap: 20rpx;
- }
- .report-item {
- display: flex;
- flex-direction: row;
- align-items: center;
- background-color: #F9FAFB;
- padding: 20rpx;
- border-radius: 16rpx;
- }
- .report-item:active {
- opacity: 0.8;
- }
- .report-icon {
- width: 64rpx;
- height: 64rpx;
- background-color: #FFF0F3;
- border-radius: 16rpx;
- margin-right: 16rpx;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 32rpx;
- color: #FF6B9D;
- }
- .report-info {
- flex: 1;
- display: flex;
- flex-direction: column;
- }
- .report-title {
- font-size: 28rpx;
- font-weight: 600;
- color: #1E293B;
- }
- .report-date {
- font-size: 22rpx;
- color: #64748B;
- margin-top: 4rpx;
- }
- .report-action {
- padding: 8rpx 16rpx;
- }
- .report-download {
- font-size: 24rpx;
- color: #FF6B9D;
- font-weight: 500;
- }
- /* ===== 周报模态框 ===== */
- .modal-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.5);
- z-index: 1000;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .weekly-report-modal {
- width: 90%;
- max-width: 600rpx;
- max-height: 80vh;
- background: #FFFFFF;
- border-radius: 24rpx;
- overflow: hidden;
- }
- .modal-header {
- padding: 24rpx 30rpx;
- border-bottom: 1rpx solid #F3F4F6;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- }
- .modal-title {
- font-size: 28rpx;
- font-weight: 600;
- color: #1E293B;
- }
- .modal-close {
- font-size: 32rpx;
- color: #9CA3AF;
- }
- .modal-close:active {
- opacity: 0.7;
- }
- .modal-body {
- padding: 0;
- }
- .modal-content {
- max-height: 60vh;
- padding: 24rpx 30rpx;
- }
- .modal-footer {
- padding: 20rpx 30rpx;
- border-top: 1rpx solid #F3F4F6;
- text-align: center;
- }
- .modal-btn {
- background-color: #FF6B9D;
- color: #FFFFFF;
- border: none;
- border-radius: 8rpx;
- padding: 12rpx 24rpx;
- font-size: 26rpx;
- margin: 0;
- }
- </style>
|