|
@@ -234,6 +234,37 @@
|
|
|
</view>
|
|
</view>
|
|
|
</view>
|
|
</view>
|
|
|
</scroll-view>
|
|
</scroll-view>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 共餐人选择弹层(需求7:家庭总览「生成食谱」先选共餐人,确认后生成) -->
|
|
|
|
|
+ <view class="participant-mask" v-if="showParticipantPicker" @tap="cancelParticipants">
|
|
|
|
|
+ <view class="participant-sheet" @tap.stop>
|
|
|
|
|
+ <view class="participant-sheet-header">
|
|
|
|
|
+ <text class="participant-sheet-title">选择共餐人</text>
|
|
|
|
|
+ <text class="participant-sheet-sub">为今天的食谱挑选一起用餐的家庭成员</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="participant-list">
|
|
|
|
|
+ <view class="participant-item" v-for="member in pendingParticipantOptions" :key="member.id"
|
|
|
|
|
+ :class="{'participant-item-selected': member.selected}"
|
|
|
|
|
+ @tap="toggleParticipant(member)">
|
|
|
|
|
+ <view class="participant-check" v-if="member.selected">
|
|
|
|
|
+ <text class="participant-check-icon">✓</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <text class="participant-name">{{ member.nickname }}</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="participant-empty" v-if="pendingParticipantOptions.length === 0">
|
|
|
|
|
+ <text class="participant-empty-text">暂无家庭成员可选</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="participant-actions">
|
|
|
|
|
+ <view class="participant-btn participant-btn-cancel" @tap="cancelParticipants">
|
|
|
|
|
+ <text>取消</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ <view class="participant-btn participant-btn-confirm" @tap="confirmParticipants">
|
|
|
|
|
+ <text>确认生成({{ pendingParticipantIds.length }} 人)</text>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
@@ -267,7 +298,10 @@ export default {
|
|
|
currentMemberId: null,
|
|
currentMemberId: null,
|
|
|
memberList: [],
|
|
memberList: [],
|
|
|
foodLoading: false,
|
|
foodLoading: false,
|
|
|
- refreshing: false
|
|
|
|
|
|
|
+ refreshing: false,
|
|
|
|
|
+ // ===== 共餐人选择(需求7:家庭总览生成食谱先选共餐人)=====
|
|
|
|
|
+ showParticipantPicker: false,
|
|
|
|
|
+ pendingParticipantIds: []
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
computed: {
|
|
computed: {
|
|
@@ -331,6 +365,19 @@ export default {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
return result
|
|
|
|
|
+ },
|
|
|
|
|
+ // 共餐人选择弹层列表(原始 memberList 归一化为 {id, nickname, selected},避免模板内方法调用)
|
|
|
|
|
+ pendingParticipantOptions: function() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ var ids = self.pendingParticipantIds || []
|
|
|
|
|
+ return (self.memberList || []).map(function(m) {
|
|
|
|
|
+ var id = memberIdOf(m)
|
|
|
|
|
+ return {
|
|
|
|
|
+ id: id,
|
|
|
|
|
+ nickname: m.nickname || m.roleLabel || '成员',
|
|
|
|
|
+ selected: ids.indexOf(id) >= 0
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
onLoad: function(options) {
|
|
onLoad: function(options) {
|
|
@@ -406,10 +453,18 @@ export default {
|
|
|
},
|
|
},
|
|
|
generateRecipe: function() {
|
|
generateRecipe: function() {
|
|
|
var self = this
|
|
var self = this
|
|
|
|
|
+ // 家庭总览:先弹共餐人多选,确认后再生成(需求7)
|
|
|
|
|
+ if (self.currentView === 'family') {
|
|
|
|
|
+ self.openParticipantPicker()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 个人/成员:只对当前选中人生成(participant_member_ids 只传当前成员)
|
|
|
self.loading = true
|
|
self.loading = true
|
|
|
|
|
+ var participantIds = self.currentMemberId ? [self.currentMemberId] : []
|
|
|
generateDietRecommendation({
|
|
generateDietRecommendation({
|
|
|
date: self.formatDate(new Date()),
|
|
date: self.formatDate(new Date()),
|
|
|
- meal_type: 'all'
|
|
|
|
|
|
|
+ meal_type: 'all',
|
|
|
|
|
+ participant_member_ids: JSON.stringify(participantIds)
|
|
|
}).then(function(res) {
|
|
}).then(function(res) {
|
|
|
self.loading = false
|
|
self.loading = false
|
|
|
if (res && res.data && res.data.id) {
|
|
if (res && res.data && res.data.id) {
|
|
@@ -423,6 +478,60 @@ export default {
|
|
|
uni.showToast({ title: '生成失败', icon: 'none' })
|
|
uni.showToast({ title: '生成失败', icon: 'none' })
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
|
|
+ // 打开共餐人多选弹层(默认勾选全部家庭成员)
|
|
|
|
|
+ openParticipantPicker: function() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ var ids = []
|
|
|
|
|
+ var members = self.memberList || []
|
|
|
|
|
+ for (var i = 0; i < members.length; i++) {
|
|
|
|
|
+ var id = memberIdOf(members[i])
|
|
|
|
|
+ if (id !== null && id !== undefined && ids.indexOf(id) < 0) ids.push(id)
|
|
|
|
|
+ }
|
|
|
|
|
+ self.pendingParticipantIds = ids
|
|
|
|
|
+ self.showParticipantPicker = true
|
|
|
|
|
+ },
|
|
|
|
|
+ // 多选切换共餐人
|
|
|
|
|
+ toggleParticipant: function(member) {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ if (!member) return
|
|
|
|
|
+ var id = (member.id !== null && member.id !== undefined) ? member.id : memberIdOf(member)
|
|
|
|
|
+ if (id === null || id === undefined) return
|
|
|
|
|
+ var ids = (self.pendingParticipantIds || []).slice()
|
|
|
|
|
+ var idx = ids.indexOf(id)
|
|
|
|
|
+ if (idx >= 0) {
|
|
|
|
|
+ ids.splice(idx, 1)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ids.push(id)
|
|
|
|
|
+ }
|
|
|
|
|
+ self.pendingParticipantIds = ids
|
|
|
|
|
+ },
|
|
|
|
|
+ // 确认共餐人 → 生成食谱(participant_member_ids = 所选共餐人)
|
|
|
|
|
+ confirmParticipants: function() {
|
|
|
|
|
+ var self = this
|
|
|
|
|
+ self.showParticipantPicker = false
|
|
|
|
|
+ self.loading = true
|
|
|
|
|
+ generateDietRecommendation({
|
|
|
|
|
+ date: self.formatDate(new Date()),
|
|
|
|
|
+ meal_type: 'all',
|
|
|
|
|
+ participant_member_ids: JSON.stringify(self.pendingParticipantIds || [])
|
|
|
|
|
+ }).then(function(res) {
|
|
|
|
|
+ self.loading = false
|
|
|
|
|
+ if (res && res.data && res.data.id) {
|
|
|
|
|
+ uni.showToast({ title: '食谱生成成功', icon: 'success' })
|
|
|
|
|
+ self.loadRecommendation()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ uni.showToast({ title: (res && res.message) || '生成失败', icon: 'none' })
|
|
|
|
|
+ }
|
|
|
|
|
+ }).catch(function() {
|
|
|
|
|
+ self.loading = false
|
|
|
|
|
+ uni.showToast({ title: '生成失败', icon: 'none' })
|
|
|
|
|
+ })
|
|
|
|
|
+ },
|
|
|
|
|
+ // 取消共餐人选择
|
|
|
|
|
+ cancelParticipants: function() {
|
|
|
|
|
+ this.showParticipantPicker = false
|
|
|
|
|
+ this.pendingParticipantIds = []
|
|
|
|
|
+ },
|
|
|
likeDish: function(mealName, dishIndex) {
|
|
likeDish: function(mealName, dishIndex) {
|
|
|
uni.showToast({ title: '已点赞', icon: 'success' })
|
|
uni.showToast({ title: '已点赞', icon: 'success' })
|
|
|
},
|
|
},
|
|
@@ -1173,4 +1282,117 @@ export default {
|
|
|
font-size: calc(26rpx * var(--font-scale, 1));
|
|
font-size: calc(26rpx * var(--font-scale, 1));
|
|
|
color: $facade-text-muted;
|
|
color: $facade-text-muted;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/* ===== 共餐人选择弹层(需求7)===== */
|
|
|
|
|
+.participant-mask {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ right: 0;
|
|
|
|
|
+ bottom: 0;
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.45);
|
|
|
|
|
+ z-index: 999;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-end;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-sheet {
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ background: $app-surface;
|
|
|
|
|
+ border-radius: 24rpx 24rpx 0 0;
|
|
|
|
|
+ padding: 32rpx 32rpx 40rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-sheet-header {
|
|
|
|
|
+ padding-bottom: 24rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-sheet-title {
|
|
|
|
|
+ font-size: calc(32rpx * var(--font-scale, 1));
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: $facade-text-strong;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-sheet-sub {
|
|
|
|
|
+ font-size: calc(24rpx * var(--font-scale, 1));
|
|
|
|
|
+ color: $facade-text-muted;
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ margin-top: 8rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 20rpx;
|
|
|
|
|
+ padding: 16rpx 0 24rpx;
|
|
|
|
|
+ max-height: 60vh;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-item {
|
|
|
|
|
+ box-sizing: border-box;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 16rpx 24rpx;
|
|
|
|
|
+ border: 2rpx solid $facade-fill-2;
|
|
|
|
|
+ border-radius: 24rpx;
|
|
|
|
|
+ background: $app-surface;
|
|
|
|
|
+ min-width: 160rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-item-selected {
|
|
|
|
|
+ border-color: $dim-body;
|
|
|
|
|
+ background: linear-gradient(135deg, $dim-body, $dim-body-light);
|
|
|
|
|
+}
|
|
|
|
|
+.participant-name {
|
|
|
|
|
+ font-size: calc(26rpx * var(--font-scale, 1));
|
|
|
|
|
+ color: $facade-text-body;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-item-selected .participant-name {
|
|
|
|
|
+ color: $app-surface;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-check {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ top: -8rpx;
|
|
|
|
|
+ right: -8rpx;
|
|
|
|
|
+ width: 32rpx;
|
|
|
|
|
+ height: 32rpx;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background-color: $app-surface;
|
|
|
|
|
+ color: $dim-body;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ z-index: 2;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-check-icon {
|
|
|
|
|
+ font-size: calc(20rpx * var(--font-scale, 1));
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-empty {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 20rpx 0;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-empty-text {
|
|
|
|
|
+ font-size: calc(24rpx * var(--font-scale, 1));
|
|
|
|
|
+ color: $facade-text-muted;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-actions {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 20rpx;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-btn {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ padding: 26rpx 0;
|
|
|
|
|
+ border-radius: 999rpx;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ font-size: calc(28rpx * var(--font-scale, 1));
|
|
|
|
|
+}
|
|
|
|
|
+.participant-btn-cancel {
|
|
|
|
|
+ background: $facade-fill-2;
|
|
|
|
|
+ color: $facade-text-body;
|
|
|
|
|
+}
|
|
|
|
|
+.participant-btn-confirm {
|
|
|
|
|
+ background: linear-gradient(135deg, $dim-body, $dim-body-light);
|
|
|
|
|
+ color: $app-surface;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|