|
|
@@ -12,7 +12,6 @@
|
|
|
<view class="card">
|
|
|
<view class="card-accent"></view>
|
|
|
<view class="card-body">
|
|
|
- <text class="card-title">记录睡眠</text>
|
|
|
<view class="form-group">
|
|
|
<text class="form-label">入睡时间 *</text>
|
|
|
<picker mode="time" :value="form.sleepTime" @change="function(e){form.sleepTime=e.detail.value}">
|
|
|
@@ -41,59 +40,6 @@
|
|
|
</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>
|
|
|
- </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>
|
|
|
- </view>
|
|
|
-
|
|
|
<!-- 提交按钮 -->
|
|
|
<view class="submit-wrap">
|
|
|
<view class="submit-btn" :class="{ 'submitting': submitting }" @tap="doCheckin">
|
|
|
@@ -130,54 +76,22 @@ export default {
|
|
|
return {
|
|
|
memberId: null,
|
|
|
submitting: false,
|
|
|
- isRecording: false,
|
|
|
- recordSeconds: 0,
|
|
|
- recordTimer: null,
|
|
|
- recorderManager: null,
|
|
|
history: [],
|
|
|
form: {
|
|
|
sleepTime: '21:00',
|
|
|
wakeTime: '07:00',
|
|
|
qualityScore: 7,
|
|
|
- remark: '',
|
|
|
- photos: [],
|
|
|
- voicePath: ''
|
|
|
+ remark: ''
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
- computed: {
|
|
|
- recordDurationText: function() {
|
|
|
- if (!this.form.voicePath) return ''
|
|
|
- return '录音 ' + this.recordSeconds + '秒'
|
|
|
- }
|
|
|
- },
|
|
|
onLoad: function() {
|
|
|
var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
|
|
|
if (role !== 'child') {
|
|
|
this.memberId = uni.getStorageSync('currentChildId') || uni.getStorageSync('currentMemberId') || uni.getStorageSync('userId') || null
|
|
|
}
|
|
|
- this.recorderManager = uni.getRecorderManager()
|
|
|
- this.recorderManager.onStart(() => {
|
|
|
- this.isRecording = true
|
|
|
- this.recordSeconds = 0
|
|
|
- this.recordTimer = setInterval(() => { this.recordSeconds++ }, 1000)
|
|
|
- })
|
|
|
- this.recorderManager.onStop((res) => {
|
|
|
- this.isRecording = false
|
|
|
- if (this.recordTimer) { clearInterval(this.recordTimer); this.recordTimer = null }
|
|
|
- this.form.voicePath = res.tempFilePath
|
|
|
- this.form.voiceDuration = res.duration
|
|
|
- })
|
|
|
- this.recorderManager.onError(() => {
|
|
|
- this.isRecording = false
|
|
|
- if (this.recordTimer) { clearInterval(this.recordTimer); this.recordTimer = null }
|
|
|
- uni.showToast({ title: '录音失败', icon: 'none' })
|
|
|
- })
|
|
|
this.loadHistory()
|
|
|
},
|
|
|
- onUnload: function() {
|
|
|
- if (this.recordTimer) clearInterval(this.recordTimer)
|
|
|
- },
|
|
|
methods: {
|
|
|
goBack: function() { uni.navigateBack() },
|
|
|
formatDate: function(dateStr) {
|
|
|
@@ -204,48 +118,6 @@ export default {
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
- choosePhoto: function() {
|
|
|
- var self = this
|
|
|
- uni.chooseImage({
|
|
|
- count: 9 - self.form.photos.length,
|
|
|
- sizeType: ['compressed'],
|
|
|
- sourceType: ['album', 'camera'],
|
|
|
- success: function(res) {
|
|
|
- res.tempFilePaths.forEach(function(path) {
|
|
|
- if (self.form.photos.length < 9) self.form.photos.push(path)
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
- },
|
|
|
- getPhotoKey: function(img, idx) {
|
|
|
- return img ? img : ('p' + idx)
|
|
|
- },
|
|
|
- removePhoto: function(idx) {
|
|
|
- this.form.photos.splice(idx, 1)
|
|
|
- },
|
|
|
- toggleRecord: function() {
|
|
|
- if (this.isRecording) {
|
|
|
- this.recorderManager.stop()
|
|
|
- } else {
|
|
|
- this.recorderManager.start({ duration: 60000, format: 'mp3' })
|
|
|
- }
|
|
|
- },
|
|
|
- uploadFile: function(filePath) {
|
|
|
- var self = this
|
|
|
- return new Promise(function(resolve, reject) {
|
|
|
- uni.uploadFile({
|
|
|
- url: config.API_BASE_URL + '/api/media/upload',
|
|
|
- filePath: filePath,
|
|
|
- name: 'file',
|
|
|
- header: { 'Authorization': 'Bearer ' + uni.getStorageSync('token') },
|
|
|
- success: function(res) {
|
|
|
- try { var data = JSON.parse(res.data); resolve(data.data || data.url || '') }
|
|
|
- catch (e) { resolve(res.data || '') }
|
|
|
- },
|
|
|
- fail: function() { reject() }
|
|
|
- })
|
|
|
- })
|
|
|
- },
|
|
|
async doCheckin() {
|
|
|
var self = this
|
|
|
if (!self.form.sleepTime) { uni.showToast({ title: '请选择入睡时间', icon: 'none' }); return }
|
|
|
@@ -253,17 +125,6 @@ export default {
|
|
|
if (!self.memberId) { uni.showToast({ title: '孩子ID无效', icon: 'none' }); return }
|
|
|
self.submitting = true
|
|
|
try {
|
|
|
- var photoUrls = []
|
|
|
- if (self.form.photos && self.form.photos.length > 0) {
|
|
|
- for (var i = 0; i < self.form.photos.length; i++) {
|
|
|
- try { var url = await self.uploadFile(self.form.photos[i]) } catch(e) { url = '' }
|
|
|
- if (url) photoUrls.push(url)
|
|
|
- }
|
|
|
- }
|
|
|
- var voiceUrl = ''
|
|
|
- if (self.form.voicePath) {
|
|
|
- try { voiceUrl = await self.uploadFile(self.form.voicePath) } catch(e) { voiceUrl = '' }
|
|
|
- }
|
|
|
var sleepParts = self.form.sleepTime.split(':')
|
|
|
var wakeParts = self.form.wakeTime.split(':')
|
|
|
var sleepMin = parseInt(sleepParts[0]) * 60 + parseInt(sleepParts[1])
|
|
|
@@ -279,9 +140,7 @@ export default {
|
|
|
wakeTime: self.form.wakeTime,
|
|
|
durationMinutes: duration,
|
|
|
qualityScore: self.form.qualityScore,
|
|
|
- remark: self.form.remark || '',
|
|
|
- photoUrls: photoUrls.join(','),
|
|
|
- voiceUrl: voiceUrl
|
|
|
+ remark: self.form.remark || ''
|
|
|
},
|
|
|
header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
|
|
|
success: function(res) {
|
|
|
@@ -292,9 +151,6 @@ export default {
|
|
|
self.form.wakeTime = '07:00'
|
|
|
self.form.qualityScore = 7
|
|
|
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' })
|
|
|
@@ -323,56 +179,23 @@ export default {
|
|
|
.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: 24rpx; }
|
|
|
+.card-body { padding: 28rpx; }
|
|
|
.card-title { font-size: 30rpx; font-weight: bold; color: #333; margin-bottom: 20rpx; display: block; }
|
|
|
-.form-group { margin-bottom: 20rpx; }
|
|
|
-.form-label { font-size: 26rpx; color: #333; font-weight: 500; margin-bottom: 12rpx; display: block; }
|
|
|
-.picker-display { border: 1rpx solid #ddd; border-radius: 12rpx; padding: 16rpx 20rpx; font-size: 26rpx; background: #FAFAFA; color: #333; }
|
|
|
+.form-group { margin-bottom: 24rpx; }
|
|
|
+.form-label { font-size: 28rpx; color: #333; font-weight: 600; margin-bottom: 12rpx; display: block; }
|
|
|
+.picker-display { border: 1rpx solid #ddd; border-radius: 16rpx; padding: 20rpx; font-size: 28rpx; background: #FAFAFA; color: #333; min-height: 40rpx; }
|
|
|
.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: #FF8C42; font-weight: bold; width: 40rpx; text-align: center; }
|
|
|
-.slider-max { font-size: 22rpx; color: #999; flex-shrink: 0; }
|
|
|
-.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); } }
|
|
|
+.slider-min { font-size: 24rpx; color: #999; flex-shrink: 0; }
|
|
|
+.slider-val { font-size: 28rpx; color: #FF8C42; font-weight: bold; width: 40rpx; text-align: center; }
|
|
|
+.slider-max { font-size: 24rpx; color: #999; flex-shrink: 0; }
|
|
|
+.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; }
|
|
|
.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; }
|
|
|
+.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; }
|
|
|
.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; }
|
|
|
+.history-type { font-size: 28rpx; 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-date { font-size: 24rpx; color: #999; flex-shrink: 0; margin-left: 16rpx; }
|
|
|
</style>
|