|
|
@@ -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>
|