|
|
@@ -1,19 +1,38 @@
|
|
|
<template>
|
|
|
- <scroll-view class="content" scroll-y>
|
|
|
+ <view class="page">
|
|
|
+ <!-- 顶部导航栏 -->
|
|
|
+ <view class="nav-bar">
|
|
|
+ <view class="nav-left" @tap="goBack">
|
|
|
+ <text class="nav-icon">‹</text>
|
|
|
+ <text class="nav-title">饮食打卡</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <scroll-view class="content" scroll-y>
|
|
|
<!-- 餐次选择 -->
|
|
|
<view class="card">
|
|
|
- <view class="card-accent"></view>
|
|
|
+ <view class="card-header">
|
|
|
+ <text class="card-icon">🍽️</text>
|
|
|
+ <text class="card-title">选择餐次</text>
|
|
|
+ </view>
|
|
|
<view class="card-body">
|
|
|
- <view class="form-group">
|
|
|
- <text class="form-label">餐次 *</text>
|
|
|
- <view class="tag-row">
|
|
|
- <view class="tag-btn" v-for="m in mealTypes" :key="m.value"
|
|
|
- :class="form.mealType === m.value ? 'tag-active' : ''"
|
|
|
- @tap="form.mealType = m.value">
|
|
|
- <text>{{ m.label }}</text>
|
|
|
- </view>
|
|
|
+ <view class="tag-row">
|
|
|
+ <view class="tag-btn" v-for="m in mealTypes" :key="m.value"
|
|
|
+ :class="form.mealType === m.value ? 'tag-active' : ''"
|
|
|
+ @tap="form.mealType = m.value">
|
|
|
+ <text>{{ m.label }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 食物清单 & 备注 -->
|
|
|
+ <view class="card">
|
|
|
+ <view class="card-header">
|
|
|
+ <text class="card-icon">📝</text>
|
|
|
+ <text class="card-title">记录详情</text>
|
|
|
+ </view>
|
|
|
+ <view class="card-body">
|
|
|
<view class="form-group">
|
|
|
<text class="form-label">食物清单</text>
|
|
|
<textarea class="form-textarea" v-model="form.foodItems" placeholder="吃了什么?如:米饭1碗、青菜、鸡腿" maxlength="200"></textarea>
|
|
|
@@ -25,46 +44,54 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 并排两个按钮 + 展开区域 -->
|
|
|
- <view class="media-toolbar">
|
|
|
- <view class="tool-btn" @tap="choosePhoto">
|
|
|
- <text class="tool-icon">📷</text>
|
|
|
- <text class="tool-label">上传照片</text>
|
|
|
- <text class="tool-count" v-if="form.photos.length > 0">{{ form.photos.length }}/9</text>
|
|
|
- </view>
|
|
|
- <view class="tool-divider"></view>
|
|
|
- <view class="tool-btn" @tap="toggleRecord">
|
|
|
- <text class="tool-icon">🎙</text>
|
|
|
- <text class="tool-label">{{ isRecording ? '录音中…' : (form.voicePath ? '已录制' : '录制音频') }}</text>
|
|
|
- <text class="tool-count" v-if="isRecording">{{ recordSeconds }}s</text>
|
|
|
+ <!-- 媒体工具栏 -->
|
|
|
+ <view class="card">
|
|
|
+ <view class="card-header">
|
|
|
+ <text class="card-icon">📎</text>
|
|
|
+ <text class="card-title">添加媒体</text>
|
|
|
</view>
|
|
|
- </view>
|
|
|
- <!-- 照片缩略图 -->
|
|
|
- <view class="photo-strip" v-if="form.photos.length > 0">
|
|
|
- <view class="photo-item" v-for="(img, idx) in form.photos" :key="getPhotoKey(img, idx)" @tap="removePhoto(idx)">
|
|
|
- <image :src="img" mode="aspectFill" class="photo-img"/>
|
|
|
- <view class="photo-delete-overlay">
|
|
|
- <text class="delete-icon">×</text>
|
|
|
+ <view class="card-body">
|
|
|
+ <view class="media-toolbar">
|
|
|
+ <view class="tool-btn" @tap="choosePhoto">
|
|
|
+ <text class="tool-icon">📷</text>
|
|
|
+ <text class="tool-label">照片</text>
|
|
|
+ <text class="tool-count" v-if="form.photos.length > 0">{{ form.photos.length }}/9</text>
|
|
|
+ </view>
|
|
|
+ <view class="tool-divider"></view>
|
|
|
+ <view class="tool-btn" @tap="toggleRecord">
|
|
|
+ <text class="tool-icon">🎙</text>
|
|
|
+ <text class="tool-label">{{ isRecording ? '录音中…' : (form.voicePath ? '已录制' : '录音') }}</text>
|
|
|
+ <text class="tool-count" v-if="isRecording">{{ recordSeconds }}s</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <!-- 照片缩略图 -->
|
|
|
+ <view class="photo-strip" v-if="form.photos.length > 0">
|
|
|
+ <view class="photo-item" v-for="(img, idx) in form.photos" :key="getItemKey(img, idx)" @tap="removePhoto(idx)">
|
|
|
+ <image :src="img" mode="aspectFill" class="photo-img"/>
|
|
|
+ <view class="photo-delete-overlay">
|
|
|
+ <text class="delete-icon">×</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
|
|
|
- <!-- 录音状态 -->
|
|
|
- <view class="voice-strip" v-if="isRecording">
|
|
|
- <view class="waveform">
|
|
|
- <view class="wave-bar" v-for="n in 7" :key="n" :style="{animationDelay: (n * 0.1) + 's'}"></view>
|
|
|
- </view>
|
|
|
- <view class="voice-controls">
|
|
|
- <view class="mini-btn cancel-mini" @tap="cancelRecord"><text>取消</text></view>
|
|
|
- <view class="mini-btn done-mini" @tap="finishRecord"><text>完成</text></view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
- <view class="voice-strip" v-else-if="form.voicePath">
|
|
|
- <view class="voice-player">
|
|
|
- <text class="play-icon">▶</text>
|
|
|
- <text class="voice-duration">{{ recordDurationText }}</text>
|
|
|
+ <!-- 录音状态 -->
|
|
|
+ <view class="voice-strip" v-if="isRecording">
|
|
|
+ <view class="waveform">
|
|
|
+ <view class="wave-bar" v-for="n in 7" :key="n" :style="{animationDelay: (n * 0.1) + 's'}"></view>
|
|
|
+ </view>
|
|
|
+ <view class="voice-controls">
|
|
|
+ <view class="mini-btn cancel-mini" @tap="cancelRecord"><text>取消</text></view>
|
|
|
+ <view class="mini-btn done-mini" @tap="finishRecord"><text>完成</text></view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="voice-strip" v-else-if="form.voicePath">
|
|
|
+ <view class="voice-player">
|
|
|
+ <text class="play-icon">▶</text>
|
|
|
+ <text class="voice-duration">{{ recordDurationText }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="delete-voice" @tap="deleteVoice"><text>×</text></view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
- <view class="delete-voice" @tap="deleteVoice"><text>×</text></view>
|
|
|
</view>
|
|
|
|
|
|
<!-- 提交按钮 -->
|
|
|
@@ -76,9 +103,11 @@
|
|
|
|
|
|
<!-- 历史记录 -->
|
|
|
<view class="card" v-if="history.length > 0">
|
|
|
- <view class="card-accent"></view>
|
|
|
- <view class="card-body">
|
|
|
+ <view class="card-header">
|
|
|
+ <text class="card-icon">📜</text>
|
|
|
<text class="card-title">今日记录</text>
|
|
|
+ </view>
|
|
|
+ <view class="card-body">
|
|
|
<view class="history-list">
|
|
|
<view class="history-item" v-for="item in history" :key="item.id">
|
|
|
<view class="history-left">
|
|
|
@@ -92,7 +121,11 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
+
|
|
|
+ <!-- 底部留白 -->
|
|
|
+ <view class="bottom-spacer"></view>
|
|
|
</scroll-view>
|
|
|
+ </view>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
@@ -293,21 +326,17 @@ export default {
|
|
|
voiceText: self.form.voiceText || ''
|
|
|
},
|
|
|
header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
|
|
- success: function(res) {
|
|
|
- self.submitting = false
|
|
|
- if (res.data && res.data.code === 200) {
|
|
|
- uni.showToast({ title: '打卡成功', icon: 'success' })
|
|
|
- self.form.mealType = ''
|
|
|
- self.form.foodItems = ''
|
|
|
- self.form.remark = ''
|
|
|
- self.form.photos = []
|
|
|
- self.form.voicePath = ''
|
|
|
- self.recordSeconds = 0
|
|
|
- self.loadHistory()
|
|
|
- } else {
|
|
|
- uni.showToast({ title: (res.data && res.data.message) || '打卡失败', icon: 'none' })
|
|
|
- }
|
|
|
- },
|
|
|
+success: function(res) {
|
|
|
+ self.submitting = false
|
|
|
+ if (res.data && res.data.code === 200) {
|
|
|
+ uni.showToast({ title: '打卡成功', icon: 'success' })
|
|
|
+ setTimeout(function() {
|
|
|
+ uni.navigateBack()
|
|
|
+ }, 800)
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: (res.data && res.data.message) || '打卡失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ },
|
|
|
fail: function() {
|
|
|
self.submitting = false
|
|
|
uni.showToast({ title: '网络错误', icon: 'none' })
|
|
|
@@ -323,58 +352,77 @@ export default {
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
+/* ===== 页面基础 ===== */
|
|
|
.page { min-height: 100vh; background: #FFF7ED; }
|
|
|
-.content { height: calc(100vh - 88rpx); }
|
|
|
-.card { margin: 16rpx 24rpx; background: #fff; border-radius: 20rpx; overflow: hidden; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
|
|
|
-.card-accent { height: 6rpx; background: linear-gradient(90deg, #FF8C42, #F97316); }
|
|
|
-.card-body { padding: 28rpx; }
|
|
|
-.card-title { font-size: 30rpx; font-weight: bold; color: #333; margin-bottom: 20rpx; display: block; }
|
|
|
-.form-group { margin-bottom: 24rpx; }
|
|
|
-.form-label { font-size: 28rpx; color: #333; font-weight: 600; margin-bottom: 12rpx; display: block; }
|
|
|
-.tag-row { display: flex; flex-wrap: wrap; gap: 12rpx; }
|
|
|
-.tag-btn { padding: 12rpx 28rpx; border-radius: 30rpx; border: 2rpx solid #e0e0e0; background: #fafafa; }
|
|
|
-.tag-active { border-color: #F97316; background: #FFF3E0; }
|
|
|
+.nav-bar { height: 88rpx; padding: 24rpx 24rpx 0; display: flex; align-items: flex-end; }
|
|
|
+.nav-left { display: flex; align-items: center; gap: 10rpx; }
|
|
|
+.nav-icon { font-size: 48rpx; color: #333; line-height: 1; }
|
|
|
+.nav-title { font-size: 34rpx; font-weight: 600; color: #333; }
|
|
|
+.content { padding-bottom: 40rpx; }
|
|
|
+
|
|
|
+/* ===== 卡片通用 ===== */
|
|
|
+.card { margin: 16rpx 20rpx; background: #fff; border-radius: 20rpx; overflow: hidden; box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.05); }
|
|
|
+.card-header { display: flex; align-items: center; gap: 10rpx; padding: 20rpx 24rpx 8rpx; border-bottom: 1rpx solid #f5f5f5; background: linear-gradient(90deg, #FFFAF0, #fff); }
|
|
|
+.card-icon { font-size: 32rpx; }
|
|
|
+.card-title { font-size: 30rpx; font-weight: 600; color: #333; }
|
|
|
+.card-body { padding: 20rpx 24rpx 24rpx; }
|
|
|
+
|
|
|
+/* ===== 表单 ===== */
|
|
|
+.form-group { margin-bottom: 20rpx; }
|
|
|
+.form-label { font-size: 26rpx; color: #333; font-weight: 500; margin-bottom: 10rpx; display: block; }
|
|
|
+.tag-row { display: flex; flex-wrap: wrap; gap: 10rpx; }
|
|
|
+.tag-btn { padding: 12rpx 24rpx; border-radius: 30rpx; border: 2rpx solid #e8e8e8; background: #fafafa; transition: all 0.2s ease; }
|
|
|
+.tag-active { border-color: #F97316; background: #FFF3E0; box-shadow: 0 4rpx 12rpx rgba(249,115,22,0.15); }
|
|
|
.tag-btn text { font-size: 26rpx; color: #666; }
|
|
|
-.tag-active text { font-size: 26rpx; color: #F97316; font-weight: bold; }
|
|
|
-.form-textarea { width: 100%; height: 140rpx; border: 1rpx solid #e0e0e0; border-radius: 16rpx; padding: 20rpx; font-size: 28rpx; background: #FAFAFA; box-sizing: border-box; }
|
|
|
-.form-input { width: 100%; height: 80rpx; border: 1rpx solid #e0e0e0; border-radius: 16rpx; padding: 0 20rpx; font-size: 28rpx; background: #FAFAFA; box-sizing: border-box; }
|
|
|
-/* 录入框下方的并排工具栏 */
|
|
|
-.media-toolbar { display: flex; align-items: center; margin: 16rpx 24rpx; padding: 24rpx 0; border-top: 1rpx solid #f0f0f0; }
|
|
|
-.tool-btn { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8rpx; padding: 16rpx 0; }
|
|
|
-.tool-icon { font-size: 48rpx; }
|
|
|
+.tag-active text { color: #F97316; font-weight: 600; }
|
|
|
+.form-textarea { width: 100%; height: 120rpx; border: 1rpx solid #e8e8e8; border-radius: 14rpx; padding: 16rpx; font-size: 26rpx; background: #FAFAFA; box-sizing: border-box; font-family: inherit; }
|
|
|
+.form-input { width: 100%; height: 76rpx; border: 1rpx solid #e8e8e8; border-radius: 14rpx; padding: 0 16rpx; font-size: 26rpx; background: #FAFAFA; box-sizing: border-box; }
|
|
|
+
|
|
|
+/* ===== 媒体工具栏 ===== */
|
|
|
+.media-toolbar { display: flex; align-items: center; gap: 0; padding: 12rpx 0; }
|
|
|
+.tool-btn { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6rpx; padding: 16rpx 0; }
|
|
|
+.tool-icon { font-size: 42rpx; }
|
|
|
.tool-label { font-size: 24rpx; color: #666; }
|
|
|
-.tool-count { font-size: 22rpx; color: #F97316; font-weight: bold; }
|
|
|
-.tool-divider { width: 1rpx; height: 60rpx; background: #e8e8e8; }
|
|
|
+.tool-count { font-size: 20rpx; color: #F97316; font-weight: 600; }
|
|
|
+.tool-divider { width: 1rpx; height: 56rpx; background: linear-gradient(180deg, transparent, #e8e8e8, transparent); }
|
|
|
|
|
|
-/* 照片缩略图条 */
|
|
|
-.photo-strip { display: flex; flex-wrap: wrap; gap: 12rpx; padding: 0 24rpx 4rpx; }
|
|
|
-.photo-item { position: relative; width: 140rpx; height: 140rpx; }
|
|
|
-.photo-img { width: 100%; height: 100%; border-radius: 12rpx; }
|
|
|
-.photo-delete-overlay { position: absolute; top: -6rpx; right: -6rpx; width: 36rpx; height: 36rpx; background: #EF4444; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2rpx 6rpx rgba(239,68,68,0.3); z-index: 2; }
|
|
|
-.delete-icon { color: #fff; font-size: 24rpx; font-weight: bold; line-height: 36rpx; }
|
|
|
+/* ===== 照片缩略图 ===== */
|
|
|
+.photo-strip { display: flex; flex-wrap: wrap; gap: 10rpx; padding: 12rpx 0 4rpx; }
|
|
|
+.photo-item { position: relative; width: 132rpx; height: 132rpx; border-radius: 12rpx; overflow: hidden; }
|
|
|
+.photo-img { width: 100%; height: 100%; object-fit: cover; }
|
|
|
+.photo-delete-overlay { position: absolute; top: -6rpx; right: -6rpx; width: 32rpx; height: 32rpx; background: #EF4444; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2rpx 8rpx rgba(239,68,68,0.3); z-index: 2; }
|
|
|
+.delete-icon { color: #fff; font-size: 22rpx; font-weight: bold; line-height: 32rpx; }
|
|
|
|
|
|
-/* 录音展开区 */
|
|
|
-.voice-strip { display: flex; align-items: center; justify-content: space-between; padding: 16rpx 24rpx 4rpx; gap: 16rpx; }
|
|
|
-.waveform { display: flex; align-items: center; gap: 6rpx; height: 48rpx; flex: 1; }
|
|
|
-.wave-bar { width: 6rpx; height: 10rpx; background: #F97316; border-radius: 3rpx; animation: wave-anim 0.8s ease-in-out infinite; }
|
|
|
-@keyframes wave-anim { 0%, 100% { height: 10rpx; } 50% { height: 36rpx; } }
|
|
|
-.voice-controls { display: flex; gap: 16rpx; }
|
|
|
-.mini-btn { padding: 12rpx 28rpx; border-radius: 24rpx; font-size: 26rpx; }
|
|
|
+/* ===== 录音展开区 ===== */
|
|
|
+.voice-strip { display: flex; align-items: center; justify-content: space-between; padding: 12rpx 0 4rpx; gap: 16rpx; }
|
|
|
+.waveform { display: flex; align-items: center; gap: 5rpx; height: 40rpx; flex: 1; }
|
|
|
+.wave-bar { width: 5rpx; height: 8rpx; background: #F97316; border-radius: 3rpx; animation: wave-anim 0.8s ease-in-out infinite; }
|
|
|
+@keyframes wave-anim { 0%, 100% { height: 8rpx; } 50% { height: 32rpx; } }
|
|
|
+.voice-controls { display: flex; gap: 12rpx; }
|
|
|
+.mini-btn { padding: 10rpx 24rpx; border-radius: 22rpx; font-size: 24rpx; }
|
|
|
.cancel-mini { background: #f0f0f0; color: #666; }
|
|
|
-.done-mini { background: #F97316; color: #fff; font-weight: bold; }
|
|
|
+.done-mini { background: #F97316; color: #fff; font-weight: 600; }
|
|
|
+
|
|
|
+.voice-player { display: flex; align-items: center; gap: 10rpx; flex: 1; background: #FFF3E0; padding: 10rpx 20rpx; border-radius: 28rpx; }
|
|
|
+.play-icon { font-size: 26rpx; color: #F97316; }
|
|
|
+.voice-duration { font-size: 24rpx; color: #666; }
|
|
|
+.delete-voice { width: 40rpx; height: 40rpx; display: flex; align-items: center; justify-content: center; color: #999; font-size: 28rpx; }
|
|
|
|
|
|
-.voice-player { display: flex; align-items: center; gap: 12rpx; flex: 1; background: #FFF3E0; padding: 12rpx 24rpx; border-radius: 30rpx; }
|
|
|
-.play-icon { font-size: 28rpx; color: #F97316; }
|
|
|
-.voice-duration { font-size: 26rpx; color: #666; }
|
|
|
-.delete-voice { width: 44rpx; height: 44rpx; display: flex; align-items: center; justify-content: center; color: #999; font-size: 32rpx; }
|
|
|
-.submit-wrap { padding: 24rpx 24rpx 40rpx; }
|
|
|
-.submit-btn { background: linear-gradient(135deg, #FF8C42, #F97316); color: #fff; text-align: center; padding: 24rpx 0; border-radius: 16rpx; font-size: 32rpx; font-weight: bold; }
|
|
|
+/* ===== 提交按钮 ===== */
|
|
|
+.submit-wrap { padding: 24rpx 20rpx 32rpx; }
|
|
|
+.submit-btn { background: linear-gradient(135deg, #FF8C42, #F97316); color: #fff; text-align: center; padding: 22rpx 0; border-radius: 14rpx; font-size: 30rpx; font-weight: 600; box-shadow: 0 6rpx 20rpx rgba(249,115,22,0.3); transition: all 0.2s ease; }
|
|
|
+.submit-btn:active { transform: scale(0.98); }
|
|
|
.submitting { opacity: 0.6; }
|
|
|
-.history-list { display: flex; flex-direction: column; gap: 16rpx; }
|
|
|
-.history-item { display: flex; justify-content: space-between; align-items: center; padding: 12rpx 0; border-bottom: 1rpx solid #f5f5f5; }
|
|
|
+
|
|
|
+/* ===== 历史记录 ===== */
|
|
|
+.history-list { display: flex; flex-direction: column; gap: 12rpx; }
|
|
|
+.history-item { display: flex; justify-content: space-between; align-items: center; padding: 14rpx 0; border-bottom: 1rpx solid #f5f5f5; }
|
|
|
.history-left { flex: 1; min-width: 0; }
|
|
|
-.history-type { font-size: 28rpx; font-weight: 500; color: #333; display: block; }
|
|
|
+.history-type { font-size: 26rpx; font-weight: 500; color: #333; display: block; }
|
|
|
.history-content { font-size: 24rpx; color: #888; display: block; margin-top: 4rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
|
.history-right { text-align: right; margin-left: 16rpx; flex-shrink: 0; }
|
|
|
-.history-date { font-size: 24rpx; color: #999; }
|
|
|
+.history-date { font-size: 22rpx; color: #999; }
|
|
|
+
|
|
|
+/* ===== 底部留白 ===== */
|
|
|
+.bottom-spacer { height: 40rpx; }
|
|
|
</style>
|