瀏覽代碼

feat(growth): checkin 页面合并远端 UI 改进 + 保留 getPhotoKey 修复

liaoxg 1 月之前
父節點
當前提交
e65cbe0ffe

+ 78 - 79
cfc-frontend/pages/growth/diet-checkin.vue

@@ -34,57 +34,46 @@
         </view>
       </view>
 
-      <!-- 照片上传 -->
-      <view class="card">
-        <view class="card-accent"></view>
-        <view class="card-body">
-          <view class="section-header">
-            <text class="card-title">📷 拍照记录</text>
-            <text class="photo-count">{{ form.photos.length }}/9</text>
-          </view>
-          <view class="photo-grid">
-            <view class="photo-thumb" 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">
-                <text class="photo-delete-icon">×</text>
-              </view>
-            </view>
-            <view class="photo-add-btn" v-if="form.photos.length < 9" @tap="choosePhoto">
-              <text class="camera-icon">📷</text>
-            </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>
+      </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>
         </view>
       </view>
 
-      <!-- 语音录入 -->
-      <view class="card">
-        <view class="card-accent"></view>
-        <view class="card-body">
-          <text class="card-title">🎙️ 语音打卡</text>
-          <view class="voice-recorder">
-            <view class="voice-active" v-if="isRecording">
-              <view class="waveform">
-                <view class="wave-bar wave-1"></view>
-                <view class="wave-bar wave-2"></view>
-                <view class="wave-bar wave-3"></view>
-                <view class="wave-bar wave-4"></view>
-                <view class="wave-bar wave-5"></view>
-              </view>
-              <text class="recording-time">{{ recordSeconds }}s</text>
-            </view>
-            <view class="voice-done" v-else-if="form.voicePath">
-              <text class="play-icon">▶</text>
-              <text class="done-text">{{ recordDurationText }}</text>
-            </view>
-            <view class="record-btn-wrap">
-              <view class="pulse-ring" v-if="isRecording"></view>
-              <view class="record-btn" :class="{ 'record-btn-active': isRecording }" @tap="toggleRecord">
-                <view class="record-btn-dot"></view>
-              </view>
-            </view>
-          </view>
-          <text class="voice-hint">点击开始录音</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>
 
       <!-- 提交按钮 -->
@@ -237,6 +226,20 @@ export default {
         this.recorderManager.start({ duration: 60000, format: 'mp3' })
       }
     },
+    cancelRecord: function() {
+      if (this.isRecording) {
+        this.recorderManager.stop()
+      }
+      this.form.voicePath = ''
+    },
+    finishRecord: function() {
+      if (this.isRecording) {
+        this.recorderManager.stop()
+      }
+    },
+    deleteVoice: function() {
+      this.form.voicePath = ''
+    },
     uploadFile: function(filePath) {
       var self = this
       return new Promise(function(resolve, reject) {
@@ -336,39 +339,35 @@ export default {
 .tag-active text { font-size: 24rpx; color: #F97316; font-weight: bold; }
 .form-textarea { width: 100%; height: 120rpx; border: 1rpx solid #e0e0e0; border-radius: 12rpx; padding: 16rpx; font-size: 26rpx; background: #FAFAFA; box-sizing: border-box; }
 .form-input { width: 100%; border: 1rpx solid #e0e0e0; border-radius: 12rpx; padding: 16rpx; font-size: 26rpx; background: #FAFAFA; box-sizing: border-box; }
-.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20rpx; }
-.section-header .card-title { margin-bottom: 0; }
-.photo-count { font-size: 24rpx; color: #999; }
-.photo-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
-.photo-thumb { position: relative; width: 160rpx; height: 160rpx; }
-.photo-img { width: 100%; height: 100%; border-radius: 16rpx; }
-.photo-delete { position: absolute; top: 0; right: 0; width: 36rpx; height: 36rpx; background: #EF4444; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2rpx 8rpx rgba(239,68,68,0.3); }
-.photo-delete-icon { color: #fff; font-size: 24rpx; font-weight: bold; }
-.photo-add-btn { width: 160rpx; height: 160rpx; border-radius: 16rpx; background: linear-gradient(135deg, #FF8C42, #F97316); display: flex; align-items: center; justify-content: center; box-shadow: 0 4rpx 16rpx rgba(249,115,22,0.25); }
-.camera-icon { font-size: 52rpx; }
-.voice-recorder { display: flex; flex-direction: column; align-items: center; padding: 24rpx 0 16rpx; }
-.voice-active { display: flex; flex-direction: column; align-items: center; margin-bottom: 20rpx; }
-.waveform { display: flex; align-items: center; justify-content: center; gap: 8rpx; height: 48rpx; }
-.wave-bar { width: 6rpx; height: 8rpx; background: #EF4444; border-radius: 3rpx; }
-.wave-1 { animation: waveform-anim 0.8s ease-in-out infinite 0s; }
-.wave-2 { animation: waveform-anim 0.8s ease-in-out infinite 0.1s; }
-.wave-3 { animation: waveform-anim 0.8s ease-in-out infinite 0.2s; }
-.wave-4 { animation: waveform-anim 0.8s ease-in-out infinite 0.3s; }
-.wave-5 { animation: waveform-anim 0.8s ease-in-out infinite 0.4s; }
-.recording-time { font-size: 28rpx; color: #EF4444; font-weight: bold; margin-top: 8rpx; }
-.voice-done { display: flex; align-items: center; gap: 12rpx; margin-bottom: 20rpx; padding: 12rpx 24rpx; background: #FFF7ED; border-radius: 30rpx; }
-.play-icon { font-size: 24rpx; color: #F97316; }
-.done-text { font-size: 26rpx; color: #666; }
-.record-btn-wrap { position: relative; width: 80rpx; height: 80rpx; }
-.pulse-ring { position: absolute; top: 0; left: 0; width: 80rpx; height: 80rpx; border-radius: 50%; border: 4rpx solid #EF4444; animation: pulse-expand 1.5s ease-out infinite; }
-.record-btn { position: relative; width: 80rpx; height: 80rpx; border-radius: 50%; background: linear-gradient(135deg, #FF8C42, #F97316); display: flex; align-items: center; justify-content: center; box-shadow: 0 4rpx 16rpx rgba(249,115,22,0.3); z-index: 1; }
-.record-btn-active { background: linear-gradient(135deg, #EF4444, #DC2626); box-shadow: 0 4rpx 16rpx rgba(239,68,68,0.4); animation: btn-pulse 1s ease-in-out infinite; }
-.record-btn-dot { width: 24rpx; height: 24rpx; border-radius: 50%; background: #fff; }
-.record-btn-active .record-btn-dot { width: 20rpx; height: 20rpx; border-radius: 4rpx; background: #fff; }
-.voice-hint { font-size: 22rpx; color: #999; text-align: center; }
-@keyframes waveform-anim { 0%, 100% { height: 8rpx; } 50% { height: 40rpx; } }
-@keyframes pulse-expand { 0% { transform: scale(1); opacity: 0.6; } 100% { transform: scale(1.8); opacity: 0; } }
-@keyframes btn-pulse { 0%, 100% { box-shadow: 0 4rpx 16rpx rgba(239,68,68,0.4); } 50% { box-shadow: 0 4rpx 24rpx rgba(239,68,68,0.6); } }
+/* 录入框下方的并排工具栏 */
+.media-toolbar { display: flex; align-items: center; margin-top: 16rpx; padding: 18rpx 0; border-top: 1rpx solid #f0f0f0; }
+.tool-btn { flex: 1; display: flex; align-items: center; justify-content: center; gap: 10rpx; padding: 8rpx 0; }
+.tool-icon { font-size: 36rpx; }
+.tool-label { font-size: 26rpx; color: #555; }
+.tool-count { font-size: 22rpx; color: #F97316; font-weight: bold; margin-left: 4rpx; }
+.tool-divider { width: 1rpx; height: 36rpx; background: #e8e8e8; }
+
+/* 照片缩略图条 */
+.photo-strip { display: flex; flex-wrap: wrap; gap: 12rpx; padding: 16rpx 0 4rpx; }
+.photo-item { position: relative; width: 120rpx; height: 120rpx; }
+.photo-img { width: 100%; height: 100%; border-radius: 12rpx; }
+.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 6rpx 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 0 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 24rpx; border-radius: 24rpx; font-size: 24rpx; }
+.cancel-mini { background: #f0f0f0; color: #666; }
+.done-mini { background: #F97316; color: #fff; font-weight: bold; }
+
+.voice-player { display: flex; align-items: center; gap: 12rpx; flex: 1; background: #FFF3E0; padding: 12rpx 20rpx; border-radius: 30rpx; }
+.play-icon { font-size: 28rpx; 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: 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: 30rpx; font-weight: bold; }
 .submitting { opacity: 0.6; }

+ 77 - 79
cfc-frontend/pages/growth/exercise-checkin.vue

@@ -48,58 +48,50 @@
         </view>
       </view>
 
-      <!-- 照片上传 -->
-      <view class="card">
-        <view class="card-accent"></view>
-        <view class="card-body">
-          <view class="section-header">
-            <text class="card-title">📷 拍照记录</text>
-            <text class="photo-count">{{ form.photos.length }}/9</text>
-          </view>
-          <view class="photo-grid">
-            <view class="photo-thumb" 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">
-                <text class="photo-delete-icon">×</text>
-              </view>
-            </view>
-            <view class="photo-add-btn" v-if="form.photos.length < 9" @tap="choosePhoto">
-              <text class="camera-icon">📷</text>
-            </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>
+      </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>
         </view>
       </view>
 
-      <!-- 语音录入 -->
-      <view class="card">
-        <view class="card-accent"></view>
-        <view class="card-body">
-          <text class="card-title">🎙️ 语音打卡</text>
-          <view class="voice-recorder">
-            <view class="voice-active" v-if="isRecording">
-              <view class="waveform">
-                <view class="wave-bar wave-1"></view>
-                <view class="wave-bar wave-2"></view>
-                <view class="wave-bar wave-3"></view>
-                <view class="wave-bar wave-4"></view>
-                <view class="wave-bar wave-5"></view>
-              </view>
-              <text class="recording-time">{{ recordSeconds }}s</text>
-            </view>
-            <view class="voice-done" v-else-if="form.voicePath">
-              <text class="play-icon">▶</text>
-              <text class="done-text">{{ recordDurationText }}</text>
-            </view>
-            <view class="record-btn-wrap">
-              <view class="pulse-ring" v-if="isRecording"></view>
-              <view class="record-btn" :class="{ 'record-btn-active': isRecording }" @tap="toggleRecord">
-                <view class="record-btn-dot"></view>
-              </view>
-            </view>
-          </view>
-          <text class="voice-hint">点击开始录音</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 class="submit-wrap">
@@ -256,6 +248,16 @@ export default {
         this.recorderManager.start({ duration: 60000, format: 'mp3' })
       }
     },
+    cancelRecord: function() {
+      this.recorderManager.stop()
+      this.form.voicePath = ''
+    },
+    finishRecord: function() {
+      this.recorderManager.stop()
+    },
+    deleteVoice: function() {
+      this.form.voicePath = ''
+    },
     uploadFile: function(filePath) {
       var self = this
       return new Promise(function(resolve, reject) {
@@ -355,39 +357,35 @@ export default {
 .tag-btn text { font-size: 24rpx; color: #666; }
 .tag-active text { font-size: 24rpx; color: #F97316; font-weight: bold; }
 .form-input { width: 100%; border: 1rpx solid #e0e0e0; border-radius: 12rpx; padding: 16rpx; font-size: 26rpx; background: #FAFAFA; box-sizing: border-box; }
-.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20rpx; }
-.section-header .card-title { margin-bottom: 0; }
-.photo-count { font-size: 24rpx; color: #999; }
-.photo-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
-.photo-thumb { position: relative; width: 160rpx; height: 160rpx; }
-.photo-img { width: 100%; height: 100%; border-radius: 16rpx; }
-.photo-delete { position: absolute; top: 0; right: 0; width: 36rpx; height: 36rpx; background: #EF4444; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2rpx 8rpx rgba(239,68,68,0.3); }
-.photo-delete-icon { color: #fff; font-size: 24rpx; font-weight: bold; }
-.photo-add-btn { width: 160rpx; height: 160rpx; border-radius: 16rpx; background: linear-gradient(135deg, #FF8C42, #F97316); display: flex; align-items: center; justify-content: center; box-shadow: 0 4rpx 16rpx rgba(249,115,22,0.25); }
-.camera-icon { font-size: 52rpx; }
-.voice-recorder { display: flex; flex-direction: column; align-items: center; padding: 24rpx 0 16rpx; }
-.voice-active { display: flex; flex-direction: column; align-items: center; margin-bottom: 20rpx; }
-.waveform { display: flex; align-items: center; justify-content: center; gap: 8rpx; height: 48rpx; }
-.wave-bar { width: 6rpx; height: 8rpx; background: #EF4444; border-radius: 3rpx; }
-.wave-1 { animation: waveform-anim 0.8s ease-in-out infinite 0s; }
-.wave-2 { animation: waveform-anim 0.8s ease-in-out infinite 0.1s; }
-.wave-3 { animation: waveform-anim 0.8s ease-in-out infinite 0.2s; }
-.wave-4 { animation: waveform-anim 0.8s ease-in-out infinite 0.3s; }
-.wave-5 { animation: waveform-anim 0.8s ease-in-out infinite 0.4s; }
-.recording-time { font-size: 28rpx; color: #EF4444; font-weight: bold; margin-top: 8rpx; }
-.voice-done { display: flex; align-items: center; gap: 12rpx; margin-bottom: 20rpx; padding: 12rpx 24rpx; background: #FFF7ED; border-radius: 30rpx; }
-.play-icon { font-size: 24rpx; color: #F97316; }
-.done-text { font-size: 26rpx; color: #666; }
-.record-btn-wrap { position: relative; width: 80rpx; height: 80rpx; }
-.pulse-ring { position: absolute; top: 0; left: 0; width: 80rpx; height: 80rpx; border-radius: 50%; border: 4rpx solid #EF4444; animation: pulse-expand 1.5s ease-out infinite; }
-.record-btn { position: relative; width: 80rpx; height: 80rpx; border-radius: 50%; background: linear-gradient(135deg, #FF8C42, #F97316); display: flex; align-items: center; justify-content: center; box-shadow: 0 4rpx 16rpx rgba(249,115,22,0.3); z-index: 1; }
-.record-btn-active { background: linear-gradient(135deg, #EF4444, #DC2626); box-shadow: 0 4rpx 16rpx rgba(239,68,68,0.4); animation: btn-pulse 1s ease-in-out infinite; }
-.record-btn-dot { width: 24rpx; height: 24rpx; border-radius: 50%; background: #fff; }
-.record-btn-active .record-btn-dot { width: 20rpx; height: 20rpx; border-radius: 4rpx; background: #fff; }
-.voice-hint { font-size: 22rpx; color: #999; text-align: center; }
-@keyframes waveform-anim { 0%, 100% { height: 8rpx; } 50% { height: 40rpx; } }
-@keyframes pulse-expand { 0% { transform: scale(1); opacity: 0.6; } 100% { transform: scale(1.8); opacity: 0; } }
-@keyframes btn-pulse { 0%, 100% { box-shadow: 0 4rpx 16rpx rgba(239,68,68,0.4); } 50% { box-shadow: 0 4rpx 24rpx rgba(239,68,68,0.6); } }
+/* 录入框下方的并排工具栏 */
+.media-toolbar { display: flex; align-items: center; margin-top: 16rpx; padding: 18rpx 0; border-top: 1rpx solid #f0f0f0; }
+.tool-btn { flex: 1; display: flex; align-items: center; justify-content: center; gap: 10rpx; padding: 8rpx 0; }
+.tool-icon { font-size: 36rpx; }
+.tool-label { font-size: 26rpx; color: #555; }
+.tool-count { font-size: 22rpx; color: #F97316; font-weight: bold; margin-left: 4rpx; }
+.tool-divider { width: 1rpx; height: 36rpx; background: #e8e8e8; }
+
+/* 照片缩略图条 */
+.photo-strip { display: flex; flex-wrap: wrap; gap: 12rpx; padding: 16rpx 0 4rpx; }
+.photo-item { position: relative; width: 120rpx; height: 120rpx; }
+.photo-img { width: 100%; height: 100%; border-radius: 12rpx; }
+.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 6rpx 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 0 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 24rpx; border-radius: 24rpx; font-size: 24rpx; }
+.cancel-mini { background: #f0f0f0; color: #666; }
+.done-mini { background: #F97316; color: #fff; font-weight: bold; }
+
+.voice-player { display: flex; align-items: center; gap: 12rpx; flex: 1; background: #FFF3E0; padding: 12rpx 20rpx; border-radius: 30rpx; }
+.play-icon { font-size: 28rpx; 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: 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: 30rpx; font-weight: bold; }
 .submitting { opacity: 0.6; }

+ 80 - 120
cfc-frontend/pages/growth/growth-emotion-checkin.vue

@@ -47,58 +47,9 @@
         </view>
       </view>
 
-      <!-- 照片上传 -->
-      <view class="card">
-        <view class="card-accent"></view>
-        <view class="card-body">
-          <view class="section-header">
-            <text class="card-title">📷 拍照记录</text>
-            <text class="photo-count">{{ photos.length }}/9</text>
-          </view>
-          <view class="photo-grid">
-            <view class="photo-thumb" v-for="(img, idx) in photos" :key="getPhotoKey(img, idx)" @tap="removePhoto(idx)">
-              <image :src="img" mode="aspectFill" class="photo-img" />
-              <view class="photo-delete">
-                <text class="photo-delete-icon">×</text>
-              </view>
-            </view>
-            <view class="photo-add-btn" v-if="photos.length < 9" @tap="choosePhoto">
-              <text class="camera-icon">📷</text>
-            </view>
-          </view>
-        </view>
-      </view>
 
-      <!-- 语音录入 -->
-      <view class="card">
-        <view class="card-accent"></view>
-        <view class="card-body">
-          <text class="card-title">🎙️ 语音打卡</text>
-          <view class="voice-recorder">
-            <view class="voice-active" v-if="isRecording">
-              <view class="waveform">
-                <view class="wave-bar wave-1"></view>
-                <view class="wave-bar wave-2"></view>
-                <view class="wave-bar wave-3"></view>
-                <view class="wave-bar wave-4"></view>
-                <view class="wave-bar wave-5"></view>
-              </view>
-              <text class="recording-time">{{ recordSeconds }}s</text>
-            </view>
-            <view class="voice-done" v-else-if="voicePath">
-              <text class="play-icon">▶</text>
-              <text class="done-text">{{ recordDurationText }}</text>
-            </view>
-            <view class="record-btn-wrap">
-              <view class="pulse-ring" v-if="isRecording"></view>
-              <view class="record-btn" :class="{ 'record-btn-active': isRecording }" @tap="toggleRecord">
-                <view class="record-btn-dot"></view>
-              </view>
-            </view>
-          </view>
-          <text class="voice-hint">点击开始录音</text>
-        </view>
-      </view>
+
+
 
       <!-- 提交按钮 -->
       <view class="submit-wrap">
@@ -117,7 +68,46 @@
               <view class="history-left">
                 <text class="history-type">{{ emotionIcon(item.emotionType) }} {{ emotionLabel(item.emotionType) }}</text>
                 <text class="history-content">{{ item.note || '-' }}</text>
+<!-- 并排两个按钮 + 展开区域 -->
+            <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="photos.length > 0">{{ 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 ? '录音中…' : (voicePath ? '已录制' : '录制音频') }}</text>
+                <text class="tool-count" v-if="isRecording">{{ recordSeconds }}s</text>
+              </view>
+            </view>
+            <!-- 照片缩略图 -->
+            <view class="photo-strip" v-if="photos.length > 0">
+              <view class="photo-item" v-for="(img, idx) in 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>
+              </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="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>
               <text class="history-date">{{ formatDate(item.createdAt) }}</text>
             </view>
           </view>
@@ -244,12 +234,16 @@ export default {
     removePhoto: function(idx) {
       this.photos.splice(idx, 1)
     },
-    toggleRecord: function() {
-      if (this.isRecording) {
-        this.recorderManager.stop()
-      } else {
-        this.recorderManager.start({ duration: 60000, format: 'mp3' })
-      }
+    }, 
+    cancelRecord: function() {
+      this.recorderManager.stop()
+      this.voicePath = ''
+    },
+    finishRecord: function() {
+      this.recorderManager.stop()
+    },
+    deleteVoice: function() {
+      this.voicePath = ''
     },
     uploadFile: function(filePath) {
       var self = this
@@ -326,67 +320,33 @@ export default {
 </script>
 
 <style scoped>
-.page { min-height: 100vh; background: #FFF7ED; }
-.nav-bar { display: flex; align-items: center; height: 88rpx; padding-top: env(safe-area-inset-top); background: #fff; border-bottom: 1rpx solid #f0f0f0; position: relative; }
-.nav-back { position: absolute; left: 24rpx; top: 0; bottom: 0; display: flex; align-items: center; }
-.back-text { font-size: 28rpx; color: #F97316; }
-.nav-title { flex: 1; text-align: center; font-size: 32rpx; font-weight: bold; color: #333; }
-.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, #FF6B9D, #FF8CB3); }
-.card-body { padding: 24rpx; }
-.card-title { font-size: 30rpx; font-weight: bold; color: #333; margin-bottom: 20rpx; display: block; }
-.emotion-grid { display: flex; flex-wrap: wrap; gap: 16rpx; justify-content: space-between; }
-.emotion-item { width: calc(25% - 12rpx); display: flex; flex-direction: column; align-items: center; padding: 16rpx 8rpx; border-radius: 16rpx; border: 2rpx solid #f0f0f0; }
-.emotion-active { border-color: #FF6B9D; background: #FFF0F5; }
-.emotion-icon { font-size: 44rpx; display: block; margin-bottom: 4rpx; }
-.emotion-label { font-size: 22rpx; color: #666; }
-.emotion-active .emotion-label { font-size: 22rpx; color: #FF6B9D; font-weight: bold; }
-.slider-row { display: flex; align-items: center; gap: 12rpx; }
-.slider-min { font-size: 22rpx; color: #999; flex-shrink: 0; }
-.slider-val { font-size: 24rpx; color: #FF6B9D; font-weight: bold; width: 40rpx; text-align: center; }
-.slider-max { font-size: 22rpx; color: #999; flex-shrink: 0; }
-.note-input { width: 100%; height: 160rpx; border: 1rpx solid #e0e0e0; border-radius: 12rpx; padding: 16rpx; font-size: 26rpx; background: #FAFAFA; box-sizing: border-box; }
-.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20rpx; }
-.section-header .card-title { margin-bottom: 0; }
-.photo-count { font-size: 24rpx; color: #999; }
-.photo-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
-.photo-thumb { position: relative; width: 160rpx; height: 160rpx; }
-.photo-img { width: 100%; height: 100%; border-radius: 16rpx; }
-.photo-delete { position: absolute; top: 0; right: 0; width: 36rpx; height: 36rpx; background: #EF4444; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 2rpx 8rpx rgba(239,68,68,0.3); }
-.photo-delete-icon { color: #fff; font-size: 24rpx; font-weight: bold; }
-.photo-add-btn { width: 160rpx; height: 160rpx; border-radius: 16rpx; background: linear-gradient(135deg, #FF8C42, #F97316); display: flex; align-items: center; justify-content: center; box-shadow: 0 4rpx 16rpx rgba(249,115,22,0.25); }
-.camera-icon { font-size: 52rpx; }
-.voice-recorder { display: flex; flex-direction: column; align-items: center; padding: 24rpx 0 16rpx; }
-.voice-active { display: flex; flex-direction: column; align-items: center; margin-bottom: 20rpx; }
-.waveform { display: flex; align-items: center; justify-content: center; gap: 8rpx; height: 48rpx; }
-.wave-bar { width: 6rpx; height: 8rpx; background: #EF4444; border-radius: 3rpx; }
-.wave-1 { animation: waveform-anim 0.8s ease-in-out infinite 0s; }
-.wave-2 { animation: waveform-anim 0.8s ease-in-out infinite 0.1s; }
-.wave-3 { animation: waveform-anim 0.8s ease-in-out infinite 0.2s; }
-.wave-4 { animation: waveform-anim 0.8s ease-in-out infinite 0.3s; }
-.wave-5 { animation: waveform-anim 0.8s ease-in-out infinite 0.4s; }
-.recording-time { font-size: 28rpx; color: #EF4444; font-weight: bold; margin-top: 8rpx; }
-.voice-done { display: flex; align-items: center; gap: 12rpx; margin-bottom: 20rpx; padding: 12rpx 24rpx; background: #FFF7ED; border-radius: 30rpx; }
-.play-icon { font-size: 24rpx; color: #F97316; }
-.done-text { font-size: 26rpx; color: #666; }
-.record-btn-wrap { position: relative; width: 80rpx; height: 80rpx; }
-.pulse-ring { position: absolute; top: 0; left: 0; width: 80rpx; height: 80rpx; border-radius: 50%; border: 4rpx solid #EF4444; animation: pulse-expand 1.5s ease-out infinite; }
-.record-btn { position: relative; width: 80rpx; height: 80rpx; border-radius: 50%; background: linear-gradient(135deg, #FF8C42, #F97316); display: flex; align-items: center; justify-content: center; box-shadow: 0 4rpx 16rpx rgba(249,115,22,0.3); z-index: 1; }
-.record-btn-active { background: linear-gradient(135deg, #EF4444, #DC2626); box-shadow: 0 4rpx 16rpx rgba(239,68,68,0.4); animation: btn-pulse 1s ease-in-out infinite; }
-.record-btn-dot { width: 24rpx; height: 24rpx; border-radius: 50%; background: #fff; }
-.record-btn-active .record-btn-dot { width: 20rpx; height: 20rpx; border-radius: 4rpx; background: #fff; }
-.voice-hint { font-size: 22rpx; color: #999; text-align: center; }
-@keyframes waveform-anim { 0%, 100% { height: 8rpx; } 50% { height: 40rpx; } }
-@keyframes pulse-expand { 0% { transform: scale(1); opacity: 0.6; } 100% { transform: scale(1.8); opacity: 0; } }
-@keyframes btn-pulse { 0%, 100% { box-shadow: 0 4rpx 16rpx rgba(239,68,68,0.4); } 50% { box-shadow: 0 4rpx 24rpx rgba(239,68,68,0.6); } }
-.submit-wrap { padding: 24rpx 24rpx 40rpx; }
-.submit-btn { background: linear-gradient(135deg, #FF6B9D, #FF8CB3); color: #fff; text-align: center; padding: 24rpx 0; border-radius: 16rpx; font-size: 30rpx; font-weight: bold; }
-.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-left { flex: 1; min-width: 0; }
-.history-type { font-size: 26rpx; font-weight: 500; color: #333; display: block; }
-.history-content { font-size: 22rpx; color: #888; display: block; margin-top: 4rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
-.history-date { font-size: 22rpx; color: #999; flex-shrink: 0; margin-left: 16rpx; }
+/* 录入框下方的并排工具栏 */
+.media-toolbar { display: flex; align-items: center; margin-top: 16rpx; padding: 18rpx 0; border-top: 1rpx solid #f0f0f0; }
+.tool-btn { flex: 1; display: flex; align-items: center; justify-content: center; gap: 10rpx; padding: 8rpx 0; }
+.tool-icon { font-size: 36rpx; }
+.tool-label { font-size: 26rpx; color: #555; }
+.tool-count { font-size: 22rpx; color: #F97316; font-weight: bold; margin-left: 4rpx; }
+.tool-divider { width: 1rpx; height: 36rpx; background: #e8e8e8; }
+
+/* 照片缩略图条 */
+.photo-strip { display: flex; flex-wrap: wrap; gap: 12rpx; padding: 16rpx 0 4rpx; }
+.photo-item { position: relative; width: 120rpx; height: 120rpx; }
+.photo-img { width: 100%; height: 100%; border-radius: 12rpx; }
+.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 6rpx 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 0 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 24rpx; border-radius: 24rpx; font-size: 24rpx; }
+.cancel-mini { background: #f0f0f0; color: #666; }
+.done-mini { background: #F97316; color: #fff; font-weight: bold; }
+
+.voice-player { display: flex; align-items: center; gap: 12rpx; flex: 1; background: #FFF3E0; padding: 12rpx 20rpx; border-radius: 30rpx; }
+.play-icon { font-size: 28rpx; 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: 32rpx; }
 </style>