|
|
@@ -1,655 +1,259 @@
|
|
|
<template>
|
|
|
<view class="container">
|
|
|
- <PageBanner theme="action" tagline="方案 · 执行 · 成长" quote="知行合一,每天进步一点点" />
|
|
|
-
|
|
|
- <!-- 家庭成员选择条(登录即自动建家庭,无需空态判断) -->
|
|
|
+ <PageBanner theme="action" tagline="干预 · 记录 · 成长" quote="知行合一,每天进步一点点" />
|
|
|
<FamilyMemberStrip />
|
|
|
-
|
|
|
- <!-- 今日待办卡片 -->
|
|
|
- <view class="section">
|
|
|
- <view class="todo-card">
|
|
|
- <view class="todo-header">
|
|
|
- <text class="todo-title">今日执行概览</text>
|
|
|
- </view>
|
|
|
- <view class="todo-grid">
|
|
|
- <view class="todo-item">
|
|
|
- <text class="todo-icon">✅</text>
|
|
|
- <text class="todo-label">今日打卡</text>
|
|
|
- <text class="todo-value" :class="todayCheckedIn ? 'text-success' : 'text-muted'">{{ todayCheckedIn ? '已完成' : '未完成' }}</text>
|
|
|
- </view>
|
|
|
- <view class="todo-item">
|
|
|
- <text class="todo-icon">📋</text>
|
|
|
- <text class="todo-label">待完成任务</text>
|
|
|
- <text class="todo-value">{{ pendingTasks }} 项</text>
|
|
|
- </view>
|
|
|
- <view class="todo-item">
|
|
|
- <text class="todo-icon">🔥</text>
|
|
|
- <text class="todo-label">进行中挑战</text>
|
|
|
- <text class="todo-value">{{ activeChallenges }} 个</text>
|
|
|
- </view>
|
|
|
- <view class="todo-item">
|
|
|
- <text class="todo-icon">🍽️</text>
|
|
|
- <text class="todo-label">今日饮食</text>
|
|
|
- <text class="todo-value">{{ mealCount }} 餐</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
+ <view class="reminder-bar" v-if="hasIncomplete">
|
|
|
+ <text class="reminder-text">⚠ 今日还有 {{ incompleteCount }} 项未记录</text>
|
|
|
+ <text class="reminder-close" @click="dismissReminder">✕</text>
|
|
|
+ </view>
|
|
|
+ <view class="section">
|
|
|
+ <view class="progress-card">
|
|
|
+ <view class="progress-header">
|
|
|
+ <text class="progress-title">今日干预进度</text>
|
|
|
+ <text class="progress-date">{{ todayDate }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="progress-item" v-for="(item, idx) in progressItems" :key="getProgressKey(item)">
|
|
|
+ <text class="progress-icon">{{ item.icon }}</text>
|
|
|
+ <text class="progress-label">{{ item.label }}</text>
|
|
|
+ <text class="progress-status" :class="item.done ? 'status-done' : 'status-pending'">{{ item.done ? '✔ 已完成' : '' }}</text>
|
|
|
+ <text class="progress-btn" v-if="!item.done" @click="handleProgressClick(item)">{{ item.actionText || '补录' }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="progress-footer">
|
|
|
+ <text class="footer-text">今日完成 {{ doneCount }}/{{ totalCount }}</text>
|
|
|
+ <text class="footer-streak" v-if="streakDays > 0">🔥 连续打卡 {{ streakDays }} 天</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- 功能入口网格 -->
|
|
|
- <view class="section">
|
|
|
- <view class="grid-3col">
|
|
|
- <view class="grid-item" @click="goPage('/pages/health/daily-checkin')">
|
|
|
- <view class="grid-icon" style="background:rgba(16,185,129,0.15)"><text>✅</text></view>
|
|
|
- <text class="grid-label">每日打卡</text>
|
|
|
- </view>
|
|
|
- <view class="grid-item" @click="goPage('/pages/tasks/daily-tasks')">
|
|
|
- <view class="grid-icon" style="background:rgba(99,102,241,0.15)"><text>📋</text></view>
|
|
|
- <text class="grid-label">日常任务</text>
|
|
|
- </view>
|
|
|
- <view class="grid-item" @click="goPage('/pages/health/challenge-manage')">
|
|
|
- <view class="grid-icon" style="background:rgba(239,68,68,0.15)"><text>🔥</text></view>
|
|
|
- <text class="grid-label">家庭挑战</text>
|
|
|
+ </view>
|
|
|
+ <view class="section">
|
|
|
+ <view class="tracking-card">
|
|
|
+ <view class="tracking-tabs">
|
|
|
+ <view class="tracking-tab" :class="activeTab === tab.key ? 'tab-active' : ''" v-for="tab in trackingTabs" :key="tab.key" @click="switchTab(tab.key)">
|
|
|
+ <text class="tab-text" :class="activeTab === tab.key ? 'tab-active-text' : ''">{{ tab.label }}</text>
|
|
|
</view>
|
|
|
- <view class="grid-item" @click="goPage('/pages/diet/index')">
|
|
|
- <view class="grid-icon" style="background:rgba(245,158,11,0.15)"><text>🍽️</text></view>
|
|
|
- <text class="grid-label">饮食记录</text>
|
|
|
- </view>
|
|
|
- <view class="grid-item" @click="goPage('/pages/health/exercise-index')">
|
|
|
- <view class="grid-icon" style="background:rgba(34,197,94,0.15)"><text>🏃</text></view>
|
|
|
- <text class="grid-label">运动记录</text>
|
|
|
- </view>
|
|
|
- <view class="grid-item" @click="goPage('/pages/health/sleep-index')">
|
|
|
- <view class="grid-icon" style="background:rgba(139,92,246,0.15)"><text>😴</text></view>
|
|
|
- <text class="grid-label">作息记录</text>
|
|
|
- </view>
|
|
|
- <view class="grid-item" @click="goPage('/pages/growth/detail')">
|
|
|
- <view class="grid-icon" style="background:rgba(249,115,22,0.15)"><text>📈</text></view>
|
|
|
- <text class="grid-label">成长记录</text>
|
|
|
+ </view>
|
|
|
+ <view class="tracking-content">
|
|
|
+ <view class="tracking-empty" v-if="currentRecords.length === 0">
|
|
|
+ <text class="empty-icon">📊</text>
|
|
|
+ <text class="empty-text">还没有记录</text>
|
|
|
+ <text class="empty-hint">开始记录{{ activeTabLabel }}吧</text>
|
|
|
</view>
|
|
|
- <view class="grid-item" @click="goPage('/pages/health/meditation-index')">
|
|
|
- <view class="grid-icon" style="background:rgba(255,107,157,0.15)"><text>🧘</text></view>
|
|
|
- <text class="grid-label">冥想练习</text>
|
|
|
+ <view class="tracking-record" v-for="(rec, rIdx) in currentRecords" :key="getRecordKey(rec)">
|
|
|
+ <text class="record-date">{{ rec._date || rec._text }}</text>
|
|
|
+ <text class="record-content">{{ rec._content }}</text>
|
|
|
</view>
|
|
|
+ <view class="tracking-more" v-if="currentRecords.length > 0" @click="viewAllRecords">查看全部 ›</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- 活动推荐(从首页移过来) -->
|
|
|
- <DimensionActivities
|
|
|
- :isLoggedIn="true"
|
|
|
- @activityClick="goActivityDetail"
|
|
|
- @moreActivities="goMoreActivities" />
|
|
|
-
|
|
|
- <!-- 成长记录(原有内容) -->
|
|
|
- <view class="section">
|
|
|
- <view class="section-header">
|
|
|
- <text class="section-title">成长记录</text>
|
|
|
- <text class="section-more" v-if="records.length > 0" @click="viewAllRecords">查看全部 ›</text>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 孩子选择 -->
|
|
|
- <view class="children-list" v-if="!selectedChild">
|
|
|
- <view class="child-card" v-for="child in children" :key="child.id" @click="viewChildRecords(child)">
|
|
|
- <view class="child-info">
|
|
|
- <text class="child-name">{{ child.nickname || child.name }}</text>
|
|
|
- <text class="child-age" v-if="child.age">年龄:{{ child.age }}岁</text>
|
|
|
- </view>
|
|
|
- <view class="arrow">
|
|
|
- <text class="arrow-icon">›</text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
- <!-- 档案记录 -->
|
|
|
- <view class="records-section" v-if="selectedChild">
|
|
|
- <view class="filter-tabs">
|
|
|
- <view class="filter-tab" :class="activeFilter === 'all' ? 'tab-active' : ''" @click="setFilter('all')">
|
|
|
- <text class="tab-text" :class="activeFilter === 'all' ? 'tab-active-text' : ''">全部</text>
|
|
|
- </view>
|
|
|
- <view class="filter-tab" :class="activeFilter === 'manual' ? 'tab-active' : ''" @click="setFilter('manual')">
|
|
|
- <text class="tab-text" :class="activeFilter === 'manual' ? 'tab-active-text' : ''">手动</text>
|
|
|
- </view>
|
|
|
- <view class="filter-tab" :class="activeFilter === 'assessment' ? 'tab-active' : ''" @click="setFilter('assessment')">
|
|
|
- <text class="tab-text" :class="activeFilter === 'assessment' ? 'tab-active-text' : ''">测评</text>
|
|
|
- </view>
|
|
|
- <view class="filter-tab" :class="activeFilter === 'upload' ? 'tab-active' : ''" @click="setFilter('upload')">
|
|
|
- <text class="tab-text" :class="activeFilter === 'upload' ? 'tab-active-text' : ''">上传</text>
|
|
|
- </view>
|
|
|
- <view class="filter-tab" :class="activeFilter === 'supplement' ? 'tab-active' : ''" @click="setFilter('supplement')">
|
|
|
- <text class="tab-text" :class="activeFilter === 'supplement' ? 'tab-active-text' : ''">补充</text>
|
|
|
- </view>
|
|
|
+ </view>
|
|
|
+ <view class="section">
|
|
|
+ <view class="milestone-card">
|
|
|
+ <text class="section-title-row">🏆 里程碑</text>
|
|
|
+ <view class="milestone-grid">
|
|
|
+ <view class="milestone-item">
|
|
|
+ <text class="milestone-icon">🔥</text>
|
|
|
+ <text class="milestone-value">{{ streakDays }}天</text>
|
|
|
+ <text class="milestone-label">连续打卡</text>
|
|
|
</view>
|
|
|
-
|
|
|
- <view class="record-card" v-for="record in filteredRecords" :key="record.id" @click="viewDetail(record)">
|
|
|
- <view class="record-header">
|
|
|
- <text class="record-title">{{ record.recordTitle || '成长记录' }}</text>
|
|
|
- <view class="record-badges">
|
|
|
- <text class="record-source-tag" v-if="record.sourceType">{{ sourceTypeLabel(record.sourceType) }}</text>
|
|
|
- <text class="record-tag" :class="record.status === 'completed' ? 'tag-completed' : 'tag-pending'">
|
|
|
- {{ record.status === 'completed' ? '已完成' : '待同步' }}
|
|
|
- </text>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- <view class="record-scores" v-if="record.overallScore || record.reportDate">
|
|
|
- <text class="score-item" v-if="record.overallScore">得分: {{ record.overallScore }}</text>
|
|
|
- <text class="score-item" v-if="record.reportDate">{{ record.reportDate }}</text>
|
|
|
- <text class="score-item" v-if="!record.reportDate && record.createdAt">{{ formatDate(record.createdAt) }}</text>
|
|
|
- </view>
|
|
|
+ <view class="milestone-item">
|
|
|
+ <text class="milestone-icon">🎯</text>
|
|
|
+ <text class="milestone-value">{{ activeChallenges }}</text>
|
|
|
+ <text class="milestone-label">进行中挑战</text>
|
|
|
</view>
|
|
|
- <view class="empty-state" v-if="filteredRecords.length === 0 && records.length > 0">
|
|
|
- <text class="empty-text">该类型暂无记录</text>
|
|
|
- </view>
|
|
|
- <view class="empty-state" v-if="records.length === 0">
|
|
|
- <text class="empty-text">暂无成长记录</text>
|
|
|
- <text class="empty-hint">点击下方按钮创建第一个成长记录</text>
|
|
|
+ <view class="milestone-item">
|
|
|
+ <text class="milestone-icon">📈</text>
|
|
|
+ <text class="milestone-value">{{ completeness }}%</text>
|
|
|
+ <text class="milestone-label">档案完整度</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
-
|
|
|
- <!-- 创建按钮 -->
|
|
|
- <view class="footer-btn" v-if="selectedChild">
|
|
|
- <button class="btn-create" @click="createRecord">记录成长</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="section">
|
|
|
+ <view class="survey-card">
|
|
|
+ <text class="section-title-row">📋 定期调研</text>
|
|
|
+ <view class="survey-pending" v-if="pendingSurvey">
|
|
|
+ <text class="survey-alert">⚠ 本周还未完成调研</text>
|
|
|
+ <text class="survey-title-text">{{ pendingSurvey.title }}</text>
|
|
|
+ <text class="survey-btn" @click="startSurvey">[开始填写]</text>
|
|
|
</view>
|
|
|
- <view class="footer-btn" v-if="!selectedChild && children.length > 0">
|
|
|
- <button class="btn-invite-guide" open-type="share" @click="prepareGuideInvite">邀请成长规划师</button>
|
|
|
+ <view class="survey-done" v-else-if="allDone"><text class="survey-done-text">✅ 所有调研已完成</text></view>
|
|
|
+ <view class="survey-history-list" v-if="surveyHistory.length > 0">
|
|
|
+ <text class="history-label">历史调研:</text>
|
|
|
+ <view class="history-item" v-for="(h, hIdx) in surveyHistory" :key="getHistoryKey(h)">
|
|
|
+ <text class="history-date">{{ h._date }}</text>
|
|
|
+ <text class="history-content">{{ h._content }}</text>
|
|
|
+ </view>
|
|
|
+ <text class="survey-more" @click="viewAllHistory">查看全部调研 ›</text>
|
|
|
</view>
|
|
|
+ <view class="survey-empty" v-else-if="!pendingSurvey"><text class="survey-empty-text">暂无待完成调研</text></view>
|
|
|
</view>
|
|
|
+ </view>
|
|
|
+ <DimensionActivities :isLoggedIn="true" @activityClick="goActivityDetail" @moreActivities="goMoreActivities" style="margin-bottom:60rpx" />
|
|
|
</view>
|
|
|
</template>
|
|
|
-
|
|
|
<script>
|
|
|
import PageBanner from '../../components/PageBanner.vue'
|
|
|
import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
|
|
|
import DimensionActivities from '../../components/DimensionActivities.vue'
|
|
|
-import { getChildren, getChildRecords } from '../../utils/api.js'
|
|
|
-
|
|
|
+import { healthCheckinList, getDietDailyRecords, getSurveyStatus, getSurveyHistory } from '../../utils/api.js'
|
|
|
export default {
|
|
|
- components: {
|
|
|
- PageBanner,
|
|
|
- FamilyMemberStrip,
|
|
|
- DimensionActivities
|
|
|
- },
|
|
|
- data() {
|
|
|
- return {
|
|
|
- loading: false,
|
|
|
- children: [],
|
|
|
- selectedChild: null,
|
|
|
- selectedChildName: '',
|
|
|
- records: [],
|
|
|
- memberId: '',
|
|
|
- activeFilter: 'all',
|
|
|
- shareData: null,
|
|
|
- todayCheckedIn: false,
|
|
|
- pendingTasks: 0,
|
|
|
- activeChallenges: 0,
|
|
|
- mealCount: 0
|
|
|
- }
|
|
|
- },
|
|
|
+ components: { PageBanner, FamilyMemberStrip, DimensionActivities },
|
|
|
+ data() { return { memberId: '', todayDate: '', hasIncomplete: false, incompleteCount: 0, progressItems: [], activeTab: 'diet', trackingTabs: [{ key: 'diet', label: '饮食' }, { key: 'exercise', label: '运动' }, { key: 'sleep', label: '睡眠' }, { key: 'emotion', label: '心情' }, { key: 'survey', label: '调研' }], trackingData: { diet: [], exercise: [], sleep: [], emotion: [], survey: [] }, streakDays: 0, activeChallenges: 0, completeness: 0, pendingSurvey: null, allDone: false, surveyHistory: [] } },
|
|
|
computed: {
|
|
|
- filteredRecords: function() {
|
|
|
- if (this.activeFilter === 'all') return this.records
|
|
|
- var filter = this.activeFilter
|
|
|
- return this.records.filter(function(r) { return r.sourceType === filter })
|
|
|
- }
|
|
|
- },
|
|
|
- onShareAppMessage() {
|
|
|
- if (this.shareData) {
|
|
|
- return {
|
|
|
- title: this.shareData.title,
|
|
|
- path: this.shareData.path,
|
|
|
- imageUrl: '/static/invite-card.png'
|
|
|
- }
|
|
|
- }
|
|
|
+ doneCount: function() { var c = 0; for (var i = 0; i < this.progressItems.length; i++) if (this.progressItems[i] && this.progressItems[i].done) c++; return c },
|
|
|
+ totalCount: function() { return this.progressItems.length },
|
|
|
+ activeTabLabel: function() { var m = { diet: '饮食', exercise: '运动', sleep: '睡眠', emotion: '心情', survey: '调研' }; return m[this.activeTab] || '' },
|
|
|
+ currentRecords: function() { return this.trackingData[this.activeTab] || [] }
|
|
|
},
|
|
|
onLoad(options) {
|
|
|
- this.memberId = options.memberId || ''
|
|
|
- this.loadChildren()
|
|
|
- this.loadTodoData()
|
|
|
+ var ui = uni.getStorageSync('userInfo') || {}
|
|
|
+ this.memberId = options.memberId || ui.currentMemberId || ui.memberId
|
|
|
+ var now = new Date()
|
|
|
+ this.todayDate = now.getFullYear() + '-' + this.pad(now.getMonth() + 1) + '-' + this.pad(now.getDate())
|
|
|
+ this.loadAllData()
|
|
|
},
|
|
|
+ onShow() { this.loadAllData() },
|
|
|
methods: {
|
|
|
- goPage: function(url) {
|
|
|
- uni.navigateTo({ url: url })
|
|
|
- },
|
|
|
- goActivityDetail: function(act) {
|
|
|
- uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id })
|
|
|
- },
|
|
|
- goMoreActivities: function() {
|
|
|
- uni.navigateTo({ url: '/pages/activity/index' })
|
|
|
- },
|
|
|
- viewAllRecords: function() {
|
|
|
- // 滚动到档案区域或展开全部
|
|
|
- if (this.selectedChild) {
|
|
|
- uni.navigateTo({ url: '/pages/growth/detail?memberId=' + this.selectedChild.id })
|
|
|
+ pad: function(n) { return n < 10 ? '0' + n : '' + n },
|
|
|
+ getProgressKey(item) { return item && item.label ? item.label : 'p-' + Math.random() },
|
|
|
+ getRecordKey(rec) { return rec && rec._text ? rec._text + rec._content : 'r-' + Math.random() },
|
|
|
+ getHistoryKey(h) { return h && h._date ? h._date + h._content : 'h-' + Math.random() },
|
|
|
+ dismissReminder: function() { this.hasIncomplete = false },
|
|
|
+ switchTab: function(key) { this.activeTab = key; this.loadTrackingData(key) },
|
|
|
+ handleProgressClick: function(item) {
|
|
|
+ var m = { checkin: '/pages/health/daily-checkin', diet: '/pages/health/diet-index', exercise: '/pages/health/exercise-index', sleep: '/pages/health/sleep-index', emotion: '/pages/mind/index' }
|
|
|
+ var url = m[item.key]
|
|
|
+ if (item.key === 'survey' && this.pendingSurvey && this.pendingSurvey.templateId) {
|
|
|
+ url = '/pages/health/survey-questionnaire?templateId=' + this.pendingSurvey.templateId + '&templateTitle=' + encodeURIComponent(this.pendingSurvey.title) + '&templateDimension=' + (this.pendingSurvey.dimension || '')
|
|
|
}
|
|
|
+ if (url) uni.navigateTo({ url: url })
|
|
|
},
|
|
|
- sourceTypeLabel: function(type) {
|
|
|
- var map = { 'manual': '手动', 'assessment': '测评', 'upload': '上传', 'supplement': '补充' }
|
|
|
- return map[type] || type
|
|
|
- },
|
|
|
- setFilter: function(type) {
|
|
|
- this.activeFilter = type
|
|
|
+ startSurvey: function() { if (this.pendingSurvey && this.pendingSurvey.templateId) uni.navigateTo({ url: '/pages/health/survey-questionnaire?templateId=' + this.pendingSurvey.templateId + '&templateTitle=' + encodeURIComponent(this.pendingSurvey.title) + '&templateDimension=' + (this.pendingSurvey.dimension || '') }) },
|
|
|
+ viewAllRecords: function() { var m = { diet: '/pages/health/diet-index', exercise: '/pages/health/exercise-index', sleep: '/pages/health/sleep-index', survey: '/pages/health/survey-history?memberId=' + this.memberId, emotion: '/pages/mind/index' }; if (m[this.activeTab]) uni.navigateTo({ url: m[this.activeTab] }) },
|
|
|
+ viewAllHistory: function() { uni.navigateTo({ url: '/pages/health/survey-history?memberId=' + this.memberId }) },
|
|
|
+ goActivityDetail: function(act) { uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id }) },
|
|
|
+ goMoreActivities: function() { uni.navigateTo({ url: '/pages/activity/index' }) },
|
|
|
+ loadTrackingData: function(key) { return this.trackingData[key] || [] },
|
|
|
+ async loadAllData() {
|
|
|
+ var self = this
|
|
|
+ try { await self.loadProgressItems() } catch(e) {}
|
|
|
+ try { await self.loadSurveyData() } catch(e) {}
|
|
|
+ try { await self.loadMilestones() } catch(e) {}
|
|
|
},
|
|
|
- loadTodoData: function() {
|
|
|
+ async loadProgressItems() {
|
|
|
var self = this
|
|
|
+ var items = []
|
|
|
+ // 今日打卡
|
|
|
+ var checkinDone = false
|
|
|
try {
|
|
|
- var api = require('../../utils/api.js')
|
|
|
- // 检查打卡状态
|
|
|
- if (api.getCheckinStats) {
|
|
|
- api.getCheckinStats().then(function(res) {
|
|
|
- if (res.code === 200 && res.data) {
|
|
|
- self.todayCheckedIn = !!(res.data.todayCheckedIn || res.data.checkedIn || (res.data.today && res.data.today.checkedIn))
|
|
|
- }
|
|
|
- }).catch(function() {})
|
|
|
- }
|
|
|
- // 待完成任务数
|
|
|
- if (api.getTodayTasks && self.children && self.children.length > 0) {
|
|
|
- var childId = self.selectedChild ? self.selectedChild.id : self.children[0].id
|
|
|
- api.getTodayTasks(childId).then(function(res) {
|
|
|
- if (res.code === 200) {
|
|
|
- self.pendingTasks = (res.data && res.data.length) || 0
|
|
|
- }
|
|
|
- }).catch(function() {})
|
|
|
- }
|
|
|
- // 进行中挑战
|
|
|
- if (api.getChallengeList) {
|
|
|
- api.getChallengeList({ status: 'active' }).then(function(res) {
|
|
|
- if (res.code === 200) {
|
|
|
- self.activeChallenges = (res.data && res.data.length) || 0
|
|
|
- }
|
|
|
- }).catch(function() {})
|
|
|
- }
|
|
|
- // 饮食记录数
|
|
|
- if (api.getMealLogs) {
|
|
|
- api.getMealLogs({ date: new Date().toISOString().slice(0, 10) }).then(function(res) {
|
|
|
- if (res.code === 200) {
|
|
|
- self.mealCount = (res.data && res.data.length) || 0
|
|
|
- }
|
|
|
- }).catch(function() {})
|
|
|
+ var ciRes = await healthCheckinList({ memberId: self.memberId, yearMonth: self.todayDate.substring(0, 7) })
|
|
|
+ if (ciRes.code === 200 && ciRes.data) {
|
|
|
+ for (var ci = 0; ci < ciRes.data.length; ci++) { if (ciRes.data[ci] && ciRes.data[ci].checkinDate && ciRes.data[ci].checkinDate.substring(0, 10) === self.todayDate) { checkinDone = true; break } }
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
- console.error('加载待办数据失败', e)
|
|
|
- }
|
|
|
+ } catch(e) {}
|
|
|
+ items.push({ key: 'checkin', icon: '✅', label: '今日打卡', done: checkinDone, actionText: '打卡' })
|
|
|
+ // 饮食
|
|
|
+ var dietDone = false
|
|
|
+ try {
|
|
|
+ var dRes = await getDietDailyRecords({ memberId: self.memberId, date: self.todayDate })
|
|
|
+ if (dRes.code === 200 && dRes.data && dRes.data.length > 0) dietDone = true
|
|
|
+ } catch(e) {}
|
|
|
+ items.push({ key: 'diet', icon: '🍽️', label: '饮食记录', done: dietDone, actionText: '补录' })
|
|
|
+ items.push({ key: 'exercise', icon: '🏃', label: '运动记录', done: false, actionText: '记录' })
|
|
|
+ items.push({ key: 'emotion', icon: '😊', label: '心情记录', done: false, actionText: '记录' })
|
|
|
+ items.push({ key: 'survey', icon: '📋', label: '本周调研', done: self.allDone, actionText: self.allDone ? '' : '开始' })
|
|
|
+ self.progressItems = items
|
|
|
+ self.incompleteCount = 0
|
|
|
+ for (var pi = 0; pi < items.length; pi++) if (items[pi] && !items[pi].done) self.incompleteCount++
|
|
|
+ self.hasIncomplete = self.incompleteCount > 0
|
|
|
},
|
|
|
- async loadChildren() {
|
|
|
- this.loading = true
|
|
|
+ async loadSurveyData() {
|
|
|
+ var self = this
|
|
|
try {
|
|
|
- var res = await getChildren()
|
|
|
- if (res.code === 200) {
|
|
|
- this.children = res.data || []
|
|
|
- if (this.memberId && this.children.length > 0) {
|
|
|
- var matched = this.children.find(function(c) { return String(c.id) === String(this.memberId) }.bind(this))
|
|
|
- if (matched) {
|
|
|
- this.viewChildRecords(matched)
|
|
|
- }
|
|
|
- }
|
|
|
+ var statusRes = await getSurveyStatus({ memberId: self.memberId })
|
|
|
+ if (statusRes.code === 200 && statusRes.data) {
|
|
|
+ var list = statusRes.data
|
|
|
+ self.pendingSurvey = null; self.allDone = true; var hasAny = false
|
|
|
+ for (var si = 0; si < list.length; si++) { var s = list[si]; if (!s) continue; hasAny = true; if (s.status === 'pending') { self.pendingSurvey = { templateId: s.templateId, title: s.title || '定期调研', dimension: s.dimension || '' }; self.allDone = false } }
|
|
|
+ if (!hasAny) { self.pendingSurvey = null; self.allDone = false }
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
- console.error('加载孩子列表失败', e)
|
|
|
- } finally {
|
|
|
- this.loading = false
|
|
|
- }
|
|
|
- },
|
|
|
- async viewChildRecords(child) {
|
|
|
- this.selectedChild = child
|
|
|
- this.selectedChildName = child.nickname || child.name
|
|
|
- this.loading = true
|
|
|
+ } catch(e) {}
|
|
|
try {
|
|
|
- var res = await getChildRecords(child.id)
|
|
|
- if (res.code === 200) {
|
|
|
- this.records = res.data || []
|
|
|
+ var histRes = await getSurveyHistory({ memberId: self.memberId })
|
|
|
+ if (histRes.code === 200 && histRes.data) {
|
|
|
+ var hlist = histRes.data; self.surveyHistory = []
|
|
|
+ for (var hi = 0; hi < Math.min(hlist.length, 3); hi++) { var h = hlist[hi]; self.surveyHistory.push({ _date: h && h.submittedAt ? h.submittedAt.substring(0, 10) : '', _content: '已完成调研' }) }
|
|
|
}
|
|
|
- } catch (e) {
|
|
|
- console.error('加载成长记录失败', e)
|
|
|
- } finally {
|
|
|
- this.loading = false
|
|
|
- }
|
|
|
+ } catch(e) {}
|
|
|
},
|
|
|
- viewDetail: function(record) {
|
|
|
- uni.navigateTo({
|
|
|
- url: '/pages/growth/detail?recordId=' + record.id
|
|
|
- })
|
|
|
- },
|
|
|
- createRecord: function() {
|
|
|
- uni.navigateTo({
|
|
|
- url: '/pages/growth/create?memberId=' + this.selectedChild.id + '&childName=' + encodeURIComponent(this.selectedChildName)
|
|
|
- })
|
|
|
- },
|
|
|
- formatDate: function(date) {
|
|
|
- if (!date) return ''
|
|
|
- return date.substring(0, 10)
|
|
|
- },
|
|
|
- async prepareGuideInvite() {
|
|
|
+ async loadMilestones() {
|
|
|
+ var self = this
|
|
|
try {
|
|
|
- var ui = uni.getStorageSync('userInfo') || {}
|
|
|
- if (!ui.familyId) {
|
|
|
- uni.showToast({ title: '请先创建家庭', icon: 'none' })
|
|
|
- return
|
|
|
- }
|
|
|
- var userInfo = ui
|
|
|
- var generateInviteCard = require('../../utils/api.js').generateInviteCard
|
|
|
- var res = await generateInviteCard('guide')
|
|
|
- var code = res.data.code
|
|
|
- this.shareData = {
|
|
|
- title: (userInfo && userInfo.nickname ? userInfo.nickname : '家长') + ' 邀请你成为成长规划师',
|
|
|
- path: '/pages/login/login?invite_code=' + code
|
|
|
- }
|
|
|
- uni.showToast({ title: '点击右上角转发给TA', icon: 'none' })
|
|
|
- } catch (e) {
|
|
|
- uni.showToast({ title: e.message || '生成邀请失败', icon: 'none' })
|
|
|
- }
|
|
|
+ var ciRes = await healthCheckinList({ memberId: self.memberId })
|
|
|
+ if (ciRes.code === 200 && ciRes.data) self.streakDays = Math.min(ciRes.data.length, 30)
|
|
|
+ } catch(e) {}
|
|
|
+ self.activeChallenges = 0
|
|
|
+ self.completeness = self.streakDays > 0 ? Math.min(100, Math.round(self.streakDays * 10)) : 0
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
-
|
|
|
<style scoped>
|
|
|
-.container {
|
|
|
- background: #F8F8F8;
|
|
|
- min-height: 100vh;
|
|
|
- padding-bottom: 120rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.section {
|
|
|
- margin: 20rpx 24rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.section-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.section-title {
|
|
|
- font-size: 30rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-
|
|
|
-.section-more {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #F97316;
|
|
|
-}
|
|
|
-
|
|
|
-/* 今日待办卡片 */
|
|
|
-.todo-card {
|
|
|
- background: #fff;
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 24rpx;
|
|
|
- box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
|
|
|
-}
|
|
|
-
|
|
|
-.todo-header {
|
|
|
- margin-bottom: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-title {
|
|
|
- font-size: 28rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-grid {
|
|
|
- display: flex;
|
|
|
- flex-wrap: wrap;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-item {
|
|
|
- width: 50%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- padding: 10rpx 0;
|
|
|
- flex-wrap: wrap;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-icon {
|
|
|
- font-size: 28rpx;
|
|
|
- margin-right: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-label {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #999;
|
|
|
- margin-right: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.todo-value {
|
|
|
- font-size: 28rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-
|
|
|
-.text-success {
|
|
|
- color: #10B981;
|
|
|
-}
|
|
|
-
|
|
|
-.text-muted {
|
|
|
- color: #ccc;
|
|
|
-}
|
|
|
-
|
|
|
-/* 功能入口网格 3列 */
|
|
|
-.grid-3col {
|
|
|
- display: flex;
|
|
|
- flex-wrap: wrap;
|
|
|
- background: #fff;
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 8rpx 0;
|
|
|
-}
|
|
|
-
|
|
|
-.grid-item {
|
|
|
- width: 33.33%;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- padding: 20rpx 0;
|
|
|
-}
|
|
|
-
|
|
|
-.grid-icon {
|
|
|
- width: 72rpx;
|
|
|
- height: 72rpx;
|
|
|
- border-radius: 18rpx;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- margin-bottom: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.grid-icon text {
|
|
|
- font-size: 32rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.grid-item .grid-label {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #666;
|
|
|
-}
|
|
|
-
|
|
|
-/* 孩子选择 */
|
|
|
-.children-list {
|
|
|
- margin-top: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.child-card {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- background: #fff;
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 30rpx;
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.child-info {
|
|
|
- flex: 1;
|
|
|
-}
|
|
|
-
|
|
|
-.child-name {
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- display: block;
|
|
|
-}
|
|
|
-
|
|
|
-.child-age {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #999;
|
|
|
- margin-top: 6rpx;
|
|
|
- display: block;
|
|
|
-}
|
|
|
-
|
|
|
-.arrow {
|
|
|
- width: 40rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.arrow-icon {
|
|
|
- font-size: 40rpx;
|
|
|
- color: #ccc;
|
|
|
-}
|
|
|
-
|
|
|
-/* 记录筛选 */
|
|
|
-.records-section {
|
|
|
- margin-top: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.filter-tabs {
|
|
|
- display: flex;
|
|
|
- gap: 16rpx;
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.filter-tab {
|
|
|
- flex: 1;
|
|
|
- text-align: center;
|
|
|
- padding: 12rpx 0;
|
|
|
- border-radius: 20rpx;
|
|
|
- background: #fff;
|
|
|
- border: 1rpx solid #E0E0E0;
|
|
|
-}
|
|
|
-
|
|
|
-.tab-active {
|
|
|
- background: #667eea;
|
|
|
- border-color: #667eea;
|
|
|
-}
|
|
|
-
|
|
|
-.tab-text {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #666;
|
|
|
-}
|
|
|
-
|
|
|
-.tab-active-text {
|
|
|
- color: #fff;
|
|
|
-}
|
|
|
-
|
|
|
-/* 记录卡片 */
|
|
|
-.record-card {
|
|
|
- background: #fff;
|
|
|
- border-radius: 16rpx;
|
|
|
- padding: 24rpx;
|
|
|
- margin-bottom: 16rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.record-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 12rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.record-title {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #333;
|
|
|
- font-weight: bold;
|
|
|
- flex: 1;
|
|
|
-}
|
|
|
-
|
|
|
-.record-badges {
|
|
|
- display: flex;
|
|
|
- gap: 8rpx;
|
|
|
- flex-shrink: 0;
|
|
|
-}
|
|
|
-
|
|
|
-.record-source-tag {
|
|
|
- font-size: 20rpx;
|
|
|
- padding: 4rpx 10rpx;
|
|
|
- border-radius: 8rpx;
|
|
|
- background: #f0f0ff;
|
|
|
- color: #667eea;
|
|
|
-}
|
|
|
-
|
|
|
-.record-tag {
|
|
|
- font-size: 22rpx;
|
|
|
- padding: 4rpx 12rpx;
|
|
|
- border-radius: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.tag-completed {
|
|
|
- background: #e8f5e9;
|
|
|
- color: #4caf50;
|
|
|
-}
|
|
|
-
|
|
|
-.tag-pending {
|
|
|
- background: #fff3e0;
|
|
|
- color: #ff9800;
|
|
|
-}
|
|
|
-
|
|
|
-.record-scores {
|
|
|
- display: flex;
|
|
|
- gap: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.score-item {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #666;
|
|
|
-}
|
|
|
-
|
|
|
-/* 空状态 */
|
|
|
-.empty-state {
|
|
|
- text-align: center;
|
|
|
- padding: 60rpx 0;
|
|
|
-}
|
|
|
-
|
|
|
-.empty-icon {
|
|
|
- font-size: 80rpx;
|
|
|
- display: block;
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.empty-text {
|
|
|
- font-size: 28rpx;
|
|
|
- color: #999;
|
|
|
- display: block;
|
|
|
-}
|
|
|
-
|
|
|
-.empty-hint {
|
|
|
- font-size: 24rpx;
|
|
|
- color: #ccc;
|
|
|
- margin-top: 10rpx;
|
|
|
- display: block;
|
|
|
-}
|
|
|
-
|
|
|
-/* 按钮 */
|
|
|
-.footer-btn {
|
|
|
- padding: 30rpx 0;
|
|
|
-}
|
|
|
-
|
|
|
-.btn-create {
|
|
|
- width: 100%;
|
|
|
- height: 88rpx;
|
|
|
- line-height: 88rpx;
|
|
|
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
- color: #fff;
|
|
|
- border-radius: 44rpx;
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: bold;
|
|
|
- text-align: center;
|
|
|
-}
|
|
|
-
|
|
|
-.btn-invite-guide {
|
|
|
- width: 100%;
|
|
|
- height: 88rpx;
|
|
|
- line-height: 88rpx;
|
|
|
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
- color: #fff;
|
|
|
- border-radius: 44rpx;
|
|
|
- font-size: 32rpx;
|
|
|
- font-weight: bold;
|
|
|
- text-align: center;
|
|
|
-}
|
|
|
-</style>
|
|
|
+.container{background:#FFF7ED;min-height:100vh;padding-bottom:120rpx}
|
|
|
+.section{margin:16rpx 24rpx}
|
|
|
+.reminder-bar{margin:12rpx 24rpx;background:#FFF3E0;border:1rpx solid #FFB74D;border-radius:12rpx;padding:14rpx 20rpx;display:flex;align-items:center;justify-content:space-between}
|
|
|
+.reminder-text{font-size:24rpx;color:#E65100}
|
|
|
+.reminder-close{font-size:28rpx;color:#999}
|
|
|
+.progress-card{background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
|
|
|
+.progress-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16rpx}
|
|
|
+.progress-title{font-size:28rpx;font-weight:bold;color:#333}
|
|
|
+.progress-date{font-size:22rpx;color:#999}
|
|
|
+.progress-item{display:flex;align-items:center;padding:12rpx 0;border-bottom:1rpx solid #f5f5f5}
|
|
|
+.progress-item:last-of-type{border-bottom:none}
|
|
|
+.progress-icon{font-size:28rpx;margin-right:12rpx;flex-shrink:0}
|
|
|
+.progress-label{font-size:26rpx;color:#333;flex:1}
|
|
|
+.progress-status{font-size:22rpx;margin-right:8rpx;flex-shrink:0}
|
|
|
+.status-done{color:#10B981}
|
|
|
+.status-pending{color:#ccc}
|
|
|
+.progress-btn{font-size:22rpx;color:#F97316;padding:4rpx 16rpx;border:1rpx solid #F97316;border-radius:20rpx;flex-shrink:0}
|
|
|
+.progress-footer{display:flex;justify-content:space-between;align-items:center;margin-top:16rpx;padding-top:16rpx;border-top:1rpx solid #f5f5f5}
|
|
|
+.footer-text{font-size:24rpx;color:#666;font-weight:bold}
|
|
|
+.footer-streak{font-size:24rpx;color:#F97316;font-weight:bold}
|
|
|
+.tracking-card{background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
|
|
|
+.tracking-tabs{display:flex;margin-bottom:16rpx}
|
|
|
+.tracking-tab{flex:1;text-align:center;padding:10rpx 0;border-radius:20rpx;margin-right:8rpx;background:#FFF7ED}
|
|
|
+.tracking-tab:last-child{margin-right:0}
|
|
|
+.tab-active{background:#F97316}
|
|
|
+.tab-text{font-size:24rpx;color:#666}
|
|
|
+.tab-active-text{color:#fff;font-weight:bold}
|
|
|
+.tracking-content{min-height:200rpx}
|
|
|
+.tracking-empty{display:flex;flex-direction:column;align-items:center;padding:60rpx 0}
|
|
|
+.empty-icon{font-size:60rpx;margin-bottom:12rpx}
|
|
|
+.empty-text{font-size:26rpx;color:#999}
|
|
|
+.empty-hint{font-size:22rpx;color:#ccc;margin-top:8rpx}
|
|
|
+.tracking-record{display:flex;align-items:center;padding:10rpx 0;border-bottom:1rpx solid #f5f5f5}
|
|
|
+.record-date{font-size:22rpx;color:#999;width:120rpx;flex-shrink:0}
|
|
|
+.record-content{font-size:24rpx;color:#333;flex:1}
|
|
|
+.tracking-more{text-align:right;font-size:24rpx;color:#F97316;padding:12rpx 0 0 0}
|
|
|
+.milestone-card{background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
|
|
|
+.section-title-row{font-size:26rpx;font-weight:bold;color:#333;margin-bottom:16rpx;display:block}
|
|
|
+.milestone-grid{display:flex;justify-content:space-between}
|
|
|
+.milestone-item{display:flex;flex-direction:column;align-items:center;flex:1}
|
|
|
+.milestone-icon{font-size:40rpx;margin-bottom:4rpx}
|
|
|
+.milestone-value{font-size:28rpx;font-weight:bold;color:#F97316;margin-bottom:2rpx}
|
|
|
+.milestone-label{font-size:22rpx;color:#999}
|
|
|
+.survey-card{background:#fff;border-radius:20rpx;padding:24rpx;box-shadow:0 2rpx 12rpx rgba(0,0,0,0.06)}
|
|
|
+.survey-pending{background:#FFF3E0;border-radius:12rpx;padding:16rpx;margin-bottom:12rpx}
|
|
|
+.survey-alert{font-size:24rpx;color:#E65100;font-weight:bold;display:block;margin-bottom:4rpx}
|
|
|
+.survey-title-text{font-size:24rpx;color:#333;display:block;margin-bottom:8rpx}
|
|
|
+.survey-btn{font-size:22rpx;color:#F97316;font-weight:bold}
|
|
|
+.survey-done{padding:8rpx 0}
|
|
|
+.survey-done-text{font-size:24rpx;color:#10B981}
|
|
|
+.survey-history-list{border-top:1rpx solid #f5f5f5;padding-top:12rpx}
|
|
|
+.history-label{font-size:22rpx;color:#999;display:block;margin-bottom:6rpx}
|
|
|
+.history-item{display:flex;align-items:center;padding:6rpx 0}
|
|
|
+.history-date{font-size:22rpx;color:#999;width:120rpx;flex-shrink:0}
|
|
|
+.history-content{font-size:24rpx;color:#666;flex:1}
|
|
|
+.survey-more{text-align:right;font-size:22rpx;color:#F97316;padding-top:8rpx}
|
|
|
+.survey-empty{padding:12rpx 0}
|
|
|
+.survey-empty-text{font-size:24rpx;color:#999}
|
|
|
+</style>
|