Files
msh-system/msh_single_uniapp/pages/tool/checkin-copy.vue

516 lines
11 KiB
Vue
Raw Normal View History

<template>
<view class="checkin-copy-page">
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y>
<!-- 原打卡记录卡片 -->
<view class="card">
<view class="record-info">
<text class="record-label">原打卡记录</text>
<text class="record-dot"></text>
<text class="record-date">{{ originalRecord.date }} {{ originalRecord.mealTypeLabel || getMealTypeLabel(originalRecord.mealType) }}</text>
</view>
</view>
<!-- 饮食照片卡片 -->
<view class="card">
<view class="card-header">
<text class="card-title">饮食照片</text>
<text class="photo-count">已选 {{ selectedPhotoCount }}/{{ originalRecord.photos.length }}</text>
</view>
<view class="photo-grid">
<view
class="photo-item"
v-for="(photo, index) in originalRecord.photos"
:key="index"
:class="{ selected: selectedPhotos.includes(index) }"
@click="togglePhoto(index)"
>
<image class="photo-image" :src="photo" mode="aspectFill"></image>
<view class="photo-check" v-if="selectedPhotos.includes(index)">
<text class="check-icon"></text>
</view>
</view>
</view>
<view class="photo-tip">
<text>点击照片可选择/取消选择</text>
</view>
</view>
<!-- 备注说明卡片 -->
<view class="card">
<view class="card-header">
<text class="card-title">备注说明</text>
<text class="editable-label">可修改</text>
</view>
<textarea
class="remark-input"
v-model="remark"
placeholder="说点什么..."
maxlength="200"
@input="onRemarkInput"
></textarea>
<view class="char-count">{{ remark.length }}/200</view>
</view>
<!-- 生成AI营养分析视频卡片 -->
<view class="card">
<view class="card-header">
<view class="ai-header-left">
<text class="ai-icon">🎬</text>
<text class="card-title">生成AI营养分析视频</text>
</view>
<switch
class="ai-switch"
:checked="enableAIVideo"
@change="toggleAIVideo"
color="#ff6b35"
/>
</view>
<view class="ai-features" v-if="enableAIVideo">
<view class="feature-item">
<text class="feature-check"></text>
<text class="feature-text">AI自动分析营养成分</text>
</view>
<view class="feature-item">
<text class="feature-check"></text>
<text class="feature-text">生成专业营养分析视频</text>
</view>
<view class="feature-item">
<text class="feature-check"></text>
<text class="feature-text">分享到社区获得更多积分</text>
</view>
</view>
</view>
<!-- 底部安全距离 -->
<view class="safe-bottom"></view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="cancel-btn" @click="handleCancel">
<text>取消</text>
</view>
<view class="confirm-btn" @click="handleConfirm">
<text>确认打卡</text>
</view>
</view>
</view>
</template>
<script>
import { getCheckinDetail, copyCheckin, learnCheckin } from '@/api/tool.js';
export default {
data() {
return {
sourceType: '', // 'record' 或 'post',标记来源类型
sourceId: '',
originalRecord: {
date: '',
mealType: '早餐',
photos: [],
remark: ''
},
selectedPhotos: [],
remark: '',
enableAIVideo: false,
loading: false,
submitting: false
}
},
computed: {
selectedPhotoCount() {
return this.selectedPhotos.length
}
},
onLoad(options) {
// 从URL参数获取原打卡记录数据
if (options.recordId) {
this.sourceType = 'record'
this.sourceId = options.recordId
this.loadOriginalRecord(options.recordId)
} else if (options.postId) {
this.sourceType = 'post'
this.sourceId = options.postId
this.loadOriginalRecord(options.postId)
}
},
methods: {
togglePhoto(index) {
const photoIndex = this.selectedPhotos.indexOf(index)
if (photoIndex > -1) {
this.selectedPhotos.splice(photoIndex, 1)
} else {
this.selectedPhotos.push(index)
}
this.selectedPhotos.sort((a, b) => a - b)
},
onRemarkInput(e) {
this.remark = e.detail.value
},
toggleAIVideo(e) {
this.enableAIVideo = e.detail.value
},
handleCancel() {
uni.showModal({
title: '提示',
content: '确定要取消吗?',
success: (res) => {
if (res.confirm) {
uni.navigateBack()
}
}
})
},
async handleConfirm() {
if (this.selectedPhotos.length === 0) {
uni.showToast({
title: '请至少选择一张照片',
icon: 'none'
})
return
}
if (this.submitting) return
this.submitting = true
const selectedImages = this.selectedPhotos.map(index => this.originalRecord.photos[index])
const checkinData = {
photosJson: JSON.stringify(selectedImages),
mealType: this.originalRecord.mealType, // 英文值: breakfast/lunch/dinner/snack
notes: this.remark,
enableAIVideo: this.enableAIVideo
}
try {
if (this.sourceType === 'post') {
await learnCheckin(this.sourceId, checkinData)
} else {
await copyCheckin(this.sourceId, checkinData)
}
uni.showToast({
title: '打卡成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack({ delta: 2 })
}, 1500)
} catch (error) {
console.error('打卡失败:', error)
uni.showToast({
title: typeof error === 'string' ? error : '打卡失败,请重试',
icon: 'none'
})
} finally {
this.submitting = false
}
},
async loadOriginalRecord(id) {
this.loading = true
try {
const res = await getCheckinDetail(id)
const data = res.data || res
// 格式化日期
let dateStr = ''
if (data.date) {
const d = new Date(data.date)
dateStr = `${d.getMonth() + 1}${d.getDate()}`
} else if (data.createTime) {
const d = new Date(data.createTime.replace(/-/g, '/'))
dateStr = `${d.getMonth() + 1}${d.getDate()}`
}
// 解析照片(后端返回 photos 为 JSON 字符串)
let photos = []
if (data.photos) {
photos = typeof data.photos === 'string' ? JSON.parse(data.photos) : data.photos
} else if (data.images) {
photos = typeof data.images === 'string' ? JSON.parse(data.images) : data.images
} else if (data.imageList) {
photos = data.imageList
}
// 保留英文餐次类型用于提交(后端期望英文)
this.originalRecord = {
date: dateStr || '今日',
mealType: data.mealType || 'breakfast', // 保留英文值
mealTypeLabel: this.getMealTypeLabel(data.mealType), // 中文用于显示
photos: photos,
remark: data.notes || data.remark || data.description || ''
}
this.remark = this.originalRecord.remark
// 默认选中所有照片
this.selectedPhotos = this.originalRecord.photos.map((_, i) => i)
} catch (error) {
console.error('加载原打卡记录失败:', error)
uni.showToast({
title: '加载记录失败',
icon: 'none'
})
} finally {
this.loading = false
}
},
getMealTypeLabel(mealType) {
const map = {
'breakfast': '早餐',
'lunch': '午餐',
'dinner': '晚餐',
'snack': '加餐'
}
return map[mealType] || mealType || '早餐'
}
}
}
</script>
<style lang="scss" scoped>
.checkin-copy-page {
min-height: 100vh;
background: #f4f5f7;
display: flex;
flex-direction: column;
}
/* 内容滚动区域 */
.content-scroll {
flex: 1;
margin-bottom: 120rpx;
padding: 32rpx 0;
}
/* 卡片样式 */
.card {
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
margin: 0 32rpx 32rpx;
padding: 32rpx;
}
/* 原打卡记录 */
.record-info {
display: flex;
align-items: center;
gap: 16rpx;
.record-label {
font-size: 28rpx;
color: #2e3e5c;
}
.record-dot {
font-size: 32rpx;
color: #ff6b35;
line-height: 1;
}
.record-date {
font-size: 28rpx;
color: #3e5481;
}
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24rpx;
.card-title {
font-size: 28rpx;
color: #2e3e5c;
font-weight: 500;
}
.photo-count {
font-size: 24rpx;
color: #9fa5c0;
}
.editable-label {
font-size: 24rpx;
color: #9fa5c0;
}
.ai-header-left {
display: flex;
align-items: center;
gap: 16rpx;
.ai-icon {
font-size: 32rpx;
}
}
}
/* 照片网格 */
.photo-grid {
display: flex;
gap: 16rpx;
margin-bottom: 24rpx;
}
.photo-item {
width: 208rpx;
height: 208rpx;
border-radius: 24rpx;
overflow: hidden;
position: relative;
border: 2rpx solid #d0dbea;
transition: all 0.3s;
&.selected {
border-color: #ff6b35;
}
.photo-image {
width: 100%;
height: 100%;
}
.photo-check {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 107, 53, 0.2);
display: flex;
align-items: center;
justify-content: center;
.check-icon {
width: 64rpx;
height: 64rpx;
background: #ff6b35;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 40rpx;
color: #ffffff;
font-weight: bold;
}
}
}
.photo-tip {
background: #e3f2fd;
border-radius: 16rpx;
padding: 16rpx;
text-align: center;
text {
font-size: 24rpx;
color: #1976d2;
}
}
/* 备注说明 */
.remark-input {
width: 95%;
min-height: 192rpx;
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
padding: 16rpx;
font-size: 28rpx;
color: #2e3e5c;
line-height: 1.6;
margin-bottom: 16rpx;
}
.char-count {
font-size: 24rpx;
color: #9fa5c0;
}
/* AI视频功能 */
.ai-switch {
transform: scale(0.8);
}
.ai-features {
display: flex;
flex-direction: column;
gap: 16rpx;
margin-top: 24rpx;
}
.feature-item {
display: flex;
align-items: center;
gap: 16rpx;
.feature-check {
font-size: 24rpx;
color: #ff6b35;
width: 24rpx;
height: 24rpx;
display: flex;
align-items: center;
justify-content: center;
}
.feature-text {
font-size: 24rpx;
color: #3e5481;
}
}
/* 底部安全距离 */
.safe-bottom {
height: 40rpx;
}
/* 底部操作栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background: #ffffff;
border-top: 1rpx solid #d0dbea;
padding: 24rpx 32rpx;
display: flex;
align-items: center;
gap: 24rpx;
z-index: 1000;
}
.cancel-btn {
flex: 1;
height: 96rpx;
border: 2rpx solid #d0dbea;
border-radius: 50rpx;
background: #ffffff;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 28rpx;
color: #3e5481;
font-weight: 500;
}
}
.confirm-btn {
flex: 1;
height: 96rpx;
background: #ff6b35;
border-radius: 50rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(255, 107, 53, 0.3);
text {
font-size: 32rpx;
color: #ffffff;
font-weight: 500;
}
}
</style>