| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- <template>
- <view class="checkin-container">
- <!-- 顶部导航 -->
- <view class="nav-bar">
- <text class="nav-back" @click="goBack">‹ 返回</text>
- <text class="nav-title">健康打卡</text>
- <view class="nav-placeholder"></view>
- </view>
- <!-- 能量奖励提示 -->
- <view class="energy-tip">
- <text class="tip-icon">⚡</text>
- <text class="tip-text">每次打卡可获得 5 能量值</text>
- </view>
- <!-- 打卡表单 -->
- <view class="form-section">
- <view class="form-title">今日打卡</view>
- <!-- 日期选择 -->
- <view class="form-item">
- <text class="form-label">打卡日期</text>
- <view class="date-display" @click="pickDate">
- <text>{{ checkinDate }}</text>
- <text class="date-arrow">›</text>
- </view>
- </view>
- <!-- 行为选择 -->
- <view class="form-item">
- <text class="form-label">健康行为</text>
- <view class="behaviors-grid">
- <view
- class="behavior-chip"
- v-for="bh in behaviorOptions"
- :key="bh.value"
- :class="{ 'behavior-selected': selectedBehaviors.indexOf(bh.value) !== -1 }"
- @click="toggleBehavior(bh.value)"
- >
- <text>{{ bh.label }}</text>
- </view>
- </view>
- </view>
- <!-- 心情选择 -->
- <view class="form-item">
- <text class="form-label">今日心情</text>
- <view class="mood-row">
- <view
- class="mood-item"
- v-for="m in moodOptions"
- :key="m.value"
- :class="{ 'mood-selected': selectedMood === m.value }"
- @click="selectMood(m.value)"
- >
- <text class="mood-emoji">{{ m.emoji }}</text>
- <text class="mood-label">{{ m.label }}</text>
- </view>
- </view>
- </view>
- <!-- 备注 -->
- <view class="form-item">
- <text class="form-label">备注(选填)</text>
- <textarea
- class="note-input"
- v-model="note"
- placeholder="记录今天的健康小故事..."
- maxlength="200"
- ></textarea>
- </view>
- <!-- 提交按钮 -->
- <view class="submit-btn" @click="doCheckin">
- <text>提交打卡</text>
- </view>
- </view>
- <!-- 打卡记录 -->
- <view class="history-section">
- <view class="section-title">打卡记录</view>
- <view class="history-list">
- <view class="history-item" v-for="item in checkinList" :key="item.id">
- <view class="history-left">
- <text class="history-date">{{ formatDate(item.checkinDate) }}</text>
- <view class="history-behaviors">
- <text class="bh-tag" v-for="bh in parseBehaviors(item.behaviors)" :key="bh">{{ bh }}</text>
- </view>
- </view>
- <view class="history-right">
- <text class="history-mood">{{ getMoodEmoji(item.mood) }}</text>
- <text class="history-energy">+{{ item.earnedEnergy || 5 }}⚡</text>
- </view>
- </view>
- <view class="history-empty" v-if="checkinList.length === 0 && !loadingHistory">
- <text>还没有打卡记录,快来打卡吧!</text>
- </view>
- </view>
- </view>
- <view class="bottom-spacer"></view>
- </view>
- </template>
- <script>
- import config from '@/config.js'
- const healthRequest = function(url, data) {
- var token = uni.getStorageSync('token')
- return new Promise(function(resolve, reject) {
- uni.request({
- url: config.API_BASE_URL + url,
- method: 'POST',
- data: data,
- header: {
- 'Content-Type': 'application/json',
- 'Authorization': token ? 'Bearer ' + token : ''
- },
- success: function(res) {
- if (res.data.code === 200) {
- resolve(res.data)
- } else {
- reject(res.data)
- }
- },
- fail: function(err) {
- reject(err)
- }
- })
- })
- }
- export default {
- data() {
- return {
- childId: null,
- checkinDate: '',
- selectedBehaviors: [],
- selectedMood: '',
- note: '',
- checkinList: [],
- loadingHistory: false,
- behaviorOptions: [
- { label: '运动30分钟', value: '运动30分钟' },
- { label: '喝足8杯水', value: '喝足8杯水' },
- { label: '按时睡觉', value: '按时睡觉' },
- { label: '均衡饮食', value: '均衡饮食' },
- { label: '户外活动', value: '户外活动2小时' },
- { label: '视力保护', value: '视力保护' },
- { label: '情绪愉快', value: '情绪愉快' },
- { label: '无零食', value: '无零食' }
- ],
- moodOptions: [
- { label: '开心', value: 'happy', emoji: '😊' },
- { label: '平静', value: 'normal', emoji: '😐' },
- { label: '低落', value: 'sad', emoji: '😔' },
- { label: '烦躁', value: 'angry', emoji: '😤' },
- { label: '不适', value: 'sick', emoji: '🤒' }
- ]
- }
- },
- onLoad: function(options) {
- this.childId = options.childId ? parseInt(options.childId) : null
- this.checkinDate = this.formatDate(new Date())
- this.loadHistory()
- },
- methods: {
- pickDate: function() {
- var self = this
- var now = new Date()
- uni.chooseDate({
- success: function(res) {
- self.checkinDate = res.date
- }
- })
- },
- toggleBehavior: function(value) {
- var idx = this.selectedBehaviors.indexOf(value)
- if (idx === -1) {
- this.selectedBehaviors.push(value)
- } else {
- this.selectedBehaviors.splice(idx, 1)
- }
- },
- selectMood: function(value) {
- this.selectedMood = value
- },
- doCheckin: function() {
- if (this.selectedBehaviors.length === 0) {
- uni.showToast({ title: '请至少选择一项健康行为', icon: 'none' })
- return
- }
- if (!this.childId) {
- uni.showToast({ title: '孩子ID无效', icon: 'none' })
- return
- }
- var self = this
- uni.showLoading({ title: '提交中...' })
- healthRequest('/api/health/checkin/create', {
- childId: this.childId,
- checkinDate: this.checkinDate,
- behaviors: this.selectedBehaviors,
- note: this.note,
- mood: this.selectedMood
- }).then(function(res) {
- uni.hideLoading()
- uni.showModal({
- title: '打卡成功',
- content: '恭喜获得 5 能量值!',
- showCancel: false
- })
- // Reset form
- self.selectedBehaviors = []
- self.selectedMood = ''
- self.note = ''
- self.checkinDate = self.formatDate(new Date())
- // Reload history
- self.loadHistory()
- }).catch(function(e) {
- uni.hideLoading()
- if (e && e.message && e.message.indexOf('404') !== -1) {
- uni.showToast({ title: '健康打卡功能即将上线', icon: 'none' })
- } else {
- uni.showToast({ title: '打卡失败,请重试', icon: 'none' })
- }
- })
- },
- loadHistory: function() {
- if (!this.childId) return
- var self = this
- this.loadingHistory = true
- healthRequest('/api/health/checkin/list', {
- childId: this.childId
- }).then(function(res) {
- self.loadingHistory = false
- if (res.data) {
- self.checkinList = res.data
- }
- }).catch(function(e) {
- self.loadingHistory = false
- console.log('获取打卡记录失败', e)
- })
- },
- parseBehaviors: function(behaviorsStr) {
- if (!behaviorsStr) return []
- try {
- if (typeof behaviorsStr === 'string') {
- return JSON.parse(behaviorsStr)
- }
- return behaviorsStr
- } catch (e) {
- if (behaviorsStr.indexOf && behaviorsStr.indexOf('[') === 0) {
- try { return JSON.parse(behaviorsStr) } catch (e2) { return [behaviorsStr] }
- }
- return [behaviorsStr]
- }
- },
- getMoodEmoji: function(mood) {
- var map = { happy: '😊', normal: '😐', sad: '😔', angry: '😤', sick: '🤒' }
- return map[mood] || '😐'
- },
- formatDate: function(date) {
- if (!date) return ''
- if (typeof date === 'string') {
- try { date = new Date(date) } catch (e) { return date }
- }
- var y = date.getFullYear()
- var m = ('' + (date.getMonth() + 1)).padStart(2, '0')
- var d = ('' + date.getDate()).padStart(2, '0')
- return y + '-' + m + '-' + d
- },
- goBack: function() {
- uni.navigateBack()
- }
- }
- }
- </script>
- <style scoped>
- .checkin-container {
- min-height: 100vh;
- background: #f5f7fa;
- padding-bottom: 60rpx;
- }
- /* ===== 导航栏 ===== */
- .nav-bar {
- position: sticky;
- top: 0;
- background: #fff;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- padding: 20rpx 30rpx;
- box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
- z-index: 100;
- }
- .nav-back {
- font-size: 32rpx;
- color: #5B9BD5;
- }
- .nav-title {
- font-size: 32rpx;
- font-weight: bold;
- color: #333;
- }
- .nav-placeholder {
- width: 80rpx;
- }
- /* ===== 能量提示 ===== */
- .energy-tip {
- margin: 20rpx 30rpx;
- background: linear-gradient(135deg, #FFF8E1, #FFECB3);
- border-radius: 16rpx;
- padding: 16rpx 24rpx;
- display: flex;
- flex-direction: row;
- align-items: center;
- }
- .tip-icon {
- font-size: 32rpx;
- margin-right: 12rpx;
- }
- .tip-text {
- font-size: 26rpx;
- color: #E65100;
- }
- /* ===== 表单 ===== */
- .form-section {
- margin: 20rpx 30rpx;
- background: #fff;
- border-radius: 20rpx;
- padding: 24rpx;
- box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
- }
- .form-title {
- font-size: 30rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 24rpx;
- }
- .form-item {
- margin-bottom: 28rpx;
- }
- .form-label {
- font-size: 26rpx;
- color: #666;
- display: block;
- margin-bottom: 12rpx;
- }
- /* 日期 */
- .date-display {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- background: #f5f7fa;
- border-radius: 12rpx;
- padding: 16rpx 20rpx;
- font-size: 28rpx;
- color: #333;
- }
- .date-arrow {
- font-size: 32rpx;
- color: #ccc;
- }
- /* 行为选择 */
- .behaviors-grid {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- }
- .behavior-chip {
- background: #f0f0f0;
- color: #666;
- font-size: 24rpx;
- padding: 12rpx 20rpx;
- border-radius: 24rpx;
- margin-right: 16rpx;
- margin-bottom: 12rpx;
- }
- .behavior-selected {
- background: #E8F5E9;
- color: #2E7D32;
- }
- /* 心情 */
- .mood-row {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- }
- .mood-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- padding: 12rpx 16rpx;
- border-radius: 12rpx;
- }
- .mood-selected {
- background: #FFF8E1;
- }
- .mood-emoji {
- font-size: 40rpx;
- }
- .mood-label {
- font-size: 20rpx;
- color: #666;
- margin-top: 4rpx;
- }
- /* 备注 */
- .note-input {
- width: 100%;
- box-sizing: border-box;
- background: #f5f7fa;
- border-radius: 12rpx;
- padding: 16rpx;
- font-size: 28rpx;
- color: #333;
- min-height: 120rpx;
- }
- /* 提交按钮 */
- .submit-btn {
- background: linear-gradient(135deg, #4CAF50, #66BB6A);
- color: #fff;
- font-size: 32rpx;
- font-weight: bold;
- text-align: center;
- padding: 24rpx;
- border-radius: 40rpx;
- margin-top: 16rpx;
- }
- .submit-btn:active {
- opacity: 0.8;
- }
- /* ===== 历史记录 ===== */
- .history-section {
- margin: 30rpx 30rpx 0;
- }
- .section-title {
- font-size: 30rpx;
- font-weight: bold;
- color: #333;
- margin-bottom: 16rpx;
- }
- .history-list {
- display: flex;
- flex-direction: column;
- }
- .history-item {
- background: #fff;
- border-radius: 16rpx;
- padding: 20rpx;
- margin-bottom: 12rpx;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
- }
- .history-left {
- flex: 1;
- }
- .history-date {
- font-size: 26rpx;
- color: #333;
- font-weight: 500;
- display: block;
- margin-bottom: 8rpx;
- }
- .history-behaviors {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- }
- .bh-tag {
- background: #E3F2FD;
- color: #1565C0;
- font-size: 20rpx;
- padding: 4rpx 12rpx;
- border-radius: 12rpx;
- margin-right: 8rpx;
- margin-bottom: 4rpx;
- }
- .history-right {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- }
- .history-mood {
- font-size: 32rpx;
- }
- .history-energy {
- font-size: 22rpx;
- color: #4CAF50;
- margin-top: 4rpx;
- }
- .history-empty {
- text-align: center;
- padding: 40rpx;
- color: #999;
- font-size: 26rpx;
- }
- /* ===== 底部 ===== */
- .bottom-spacer {
- height: 60rpx;
- }
- </style>
|