feat:增加菜谱收藏功能

This commit is contained in:
2025-07-02 19:55:18 +08:00
parent 1037edeed4
commit 1d0cb061d0
8 changed files with 110 additions and 34 deletions

View File

@@ -34,6 +34,12 @@ export const queryRecipeByUserApi = (id: number): Promise<IRecipe[]> =>
method: 'get' method: 'get'
}) })
export const queryRecipeUserFavouriteApi = (): Promise<IRecipe[]> =>
cloudService({
url: '/food-service/food/recipe/user/favourite',
method: 'get'
})
export const queryRecipeApi = (query: IFoodQuery): Promise<IRecipe[]> => export const queryRecipeApi = (query: IFoodQuery): Promise<IRecipe[]> =>
cloudService({ cloudService({
url: '/food-service/food/recipe', url: '/food-service/food/recipe',
@@ -94,6 +100,18 @@ export const deleteRecipeLikeApi = (id: number): Promise<boolean> =>
method: 'delete' method: 'delete'
}) })
export const addRecipeFavouriteApi = (id: number): Promise<boolean> =>
cloudService({
url: `/food-service/food/recipe/${id}/favourite`,
method: 'post'
})
export const deleteRecipeFavouriteApi = (id: number): Promise<boolean> =>
cloudService({
url: `/food-service/food/recipe/${id}/favourite`,
method: 'delete'
})
export const queryRecordApi = (startDate: string, endDate: string): Promise<IRecord[]> => export const queryRecordApi = (startDate: string, endDate: string): Promise<IRecord[]> =>
cloudService({ cloudService({
url: '/food-service/food/record', url: '/food-service/food/record',

View File

@@ -39,21 +39,25 @@
<div class="bottom-container"> <div class="bottom-container">
<div class="info-list"> <div class="info-list">
<div class="info-item category-item"> <el-avatar v-if="!isCurrentUser" :src="avatarUrl" :size="20" fit="contain"/>
<span v-if="!isCurrentUser" class="user">{{ props.item.username }}</span>
<div v-if="isCurrentUser" class="info-item category-item">
<i class="fa-solid fa-utensils"></i> <i class="fa-solid fa-utensils"></i>
{{ props.item.category }} {{ props.item.category }}
</div> </div>
<div class="info-item like-item">
<div v-if="isCurrentUser" class="info-item like-item">
<i class="fa-regular fa-thumbs-up"></i> <i class="fa-regular fa-thumbs-up"></i>
{{ props.item.likeCount }} {{ props.item.likeCount }}
</div> </div>
<div class="info-item comment-item"> <div v-if="isCurrentUser" class="info-item comment-item">
<i class="fa-regular fa-comment"></i> <i class="fa-regular fa-comment"></i>
{{ props.item.commentCount }} {{ props.item.commentCount }}
</div> </div>
<div class="info-item start-item"> <div v-if="isCurrentUser" class="info-item start-item">
<i class="fa-solid fa-star"></i> <i class="fa-solid fa-star"></i>
0 {{ props.item.favouriteCount }}
</div> </div>
</div> </div>
@@ -75,17 +79,18 @@
<script setup lang="ts"> <script setup lang="ts">
import { Edit, Collection, DeleteFilled } from '@element-plus/icons-vue' import { Edit, Collection, DeleteFilled } from '@element-plus/icons-vue'
import { useFoodStore } from '@/store/food' import { useFoodStore } from '@/store/food'
import { useUserStore } from '@/store/user'
import { computed, defineProps, onMounted, reactive } from 'vue' import { computed, defineProps, onMounted, reactive } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import type { IRecipe, IRecord } from '@/types/food.ts' import type { IRecipe, IRecord } from '@/types/food.ts'
import { useRouter, useRoute } from 'vue-router' import { useRouter } from 'vue-router'
import { formatDate } from 'vue3-common/utils/dateUtil' import { formatDate } from 'vue3-common/utils/dateUtil'
import { commonElMessageBox } from 'vue3-common/utils/elUtil' import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { serviceFileRootPath } from '@/utils' import { serviceFileRootPath } from '@/utils'
const router = useRouter() const router = useRouter()
const route = useRoute()
const foodStore = useFoodStore() const foodStore = useFoodStore()
const userStore = useUserStore()
const foodCardInfo = reactive({ const foodCardInfo = reactive({
recommendRate: 0, recommendRate: 0,
@@ -103,7 +108,15 @@ onMounted(() => {
foodCardInfo.recommendRate = props.item?.recommendRate as number foodCardInfo.recommendRate = props.item?.recommendRate as number
}) })
const isCurrentUser = computed(() => route.path.includes('record')) const avatarUrl = computed(() => {
if (props.item?.avatar) {
return serviceFileRootPath + props.item.avatar
} else {
return 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
}
})
const isCurrentUser = computed(() => props.item?.userId === userStore.getUserId())
const getRecord = (recordList: IRecord[]) => { const getRecord = (recordList: IRecord[]) => {
return recordList.filter((item) => item.imageUrl) return recordList.filter((item) => item.imageUrl)

View File

@@ -4,25 +4,12 @@ import type {
IStats, IRecord, IMoment, IFoodSegment IStats, IRecord, IMoment, IFoodSegment
} from '@/types/food' } from '@/types/food'
import { import {
addRecipeApi, addRecipeApi, addRecordApi, deleteRecipeApi, deleteRecordApi,
addRecordApi, queryFoodNameListApi, queryRecipeByIdApi, queryRecordApi, queryStatsApi,
deleteRecipeApi, updateRecipeApi, updateRecordApi, queryCategoryApi, queryRecordStatsApi,
deleteRecordApi, queryCategoryStatsApi, queryRankStatsApi,
queryFoodNameListApi, queryRecipeApi, queryRecipeByUserApi, deleteRecipeLikeApi,
queryRecipeByIdApi, addRecipeLikeApi, addRecipeCommentApi, addRecipeFavouriteApi, deleteRecipeFavouriteApi, queryRecipeUserFavouriteApi
queryRecordApi,
queryStatsApi,
updateRecipeApi,
updateRecordApi,
queryCategoryApi,
queryRecordStatsApi,
queryCategoryStatsApi,
queryRankStatsApi,
queryRecipeApi,
queryRecipeByUserApi,
deleteRecipeLikeApi,
addRecipeLikeApi,
addRecipeCommentApi
} from '@/apis/food.ts' } from '@/apis/food.ts'
import type { IDialog, IHandleApi, IStatsChart } from 'vue3-common/types' import type { IDialog, IHandleApi, IStatsChart } from 'vue3-common/types'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
@@ -45,11 +32,17 @@ export const useFoodStore = defineStore('food', {
category: '', category: '',
recommendRate: 0, recommendRate: 0,
remark: '', remark: '',
isShare: false,
username: '',
userId: 0,
avatar: '',
materialList: [], materialList: [],
stepList: [], stepList: [],
recordList: [], recordList: [],
likeCount: 0, likeCount: 0,
likeList: [], likeList: [],
favouriteCount: 0,
favouriteList: [],
commentCount: 0, commentCount: 0,
commentList: [] commentList: []
} as IRecipe, } as IRecipe,
@@ -133,6 +126,11 @@ export const useFoodStore = defineStore('food', {
this.recipeList = await queryRecipeByUserApi(id) this.recipeList = await queryRecipeByUserApi(id)
this.isLoading = false this.isLoading = false
}, },
async queryRecipeUserFavourite() {
this.isLoading = true
this.recipeList = await queryRecipeUserFavouriteApi()
this.isLoading = false
},
async queryRecordList() { async queryRecordList() {
const { year, month } = this.queryRecord const { year, month } = this.queryRecord
const date = this.queryDateType === 'year' ? year : month const date = this.queryDateType === 'year' ? year : month
@@ -249,6 +247,14 @@ export const useFoodStore = defineStore('food', {
await deleteRecipeLikeApi(id) await deleteRecipeLikeApi(id)
await this.queryRecipe(this.currentRecipe.id as number) await this.queryRecipe(this.currentRecipe.id as number)
}, },
async addRecipeFavourite(id: number) {
await addRecipeFavouriteApi(id)
await this.queryRecipe(this.currentRecipe.id as number)
},
async deleteRecipeFavourite(id: number) {
await deleteRecipeFavouriteApi(id)
await this.queryRecipe(this.currentRecipe.id as number)
},
getEmptyRecipe(): IRecipe { getEmptyRecipe(): IRecipe {
return { return {
commentCount: 0, commentCount: 0,
@@ -259,6 +265,7 @@ export const useFoodStore = defineStore('food', {
materialList: [], materialList: [],
name: '', name: '',
recommendRate: 0, recommendRate: 0,
isShare: false,
remark: '', remark: '',
stepList: [], stepList: [],
recordList: [] recordList: []

View File

@@ -53,6 +53,11 @@ $card-image-height: 200px;
font-size: 1.2rem; font-size: 1.2rem;
font-weight: bold; font-weight: bold;
} }
.user {
// font-size: 0.9rem;
color: #9C9C9C;
}
} }
.category-item { .category-item {

View File

@@ -59,6 +59,10 @@ export interface IRecipe {
* 备注 * 备注
*/ */
remark: string; remark: string;
isShare: boolean;
userId?: number;
username?: string;
avatar?: string;
/** /**
* 食材 * 食材
*/ */
@@ -71,10 +75,12 @@ export interface IRecipe {
* 成果 * 成果
*/ */
recordList: IRecord[]; recordList: IRecord[];
likeList: number[]; likeList?: number[];
likeCount: number; likeCount?: number;
commentList: IComment[]; favouriteList?: number[];
commentCount: number; favouriteCount?: number;
commentList?: IComment[];
commentCount?: number;
} }
/** /**

View File

@@ -34,7 +34,7 @@
<moment-list /> <moment-list />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="菜谱收藏" name="recipe"> <el-tab-pane label="菜谱收藏" name="recipe">
<recipe-list />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="系统设置" name="system"> <el-tab-pane label="系统设置" name="system">
<div class="menu-card card-item"> <div class="menu-card card-item">
@@ -77,6 +77,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import ProfileCard from '@/components/Profile/ProfileCard.vue' import ProfileCard from '@/components/Profile/ProfileCard.vue'
import RecipeList from '@/components/Food/RecipeList.vue'
import MomentList from '@/components/Moment/MomentList.vue' import MomentList from '@/components/Moment/MomentList.vue'
import { useUserStore } from '@/store/user.ts' import { useUserStore } from '@/store/user.ts'
import { useAppStore } from '@/store/app.ts' import { useAppStore } from '@/store/app.ts'
@@ -113,6 +114,9 @@ const handleClick = async (tab: TabsPaneContext) => {
case 'moment': case 'moment':
await foodStore.queryMoment(userStore.getUserId()) await foodStore.queryMoment(userStore.getUserId())
break break
case 'recipe':
await foodStore.queryRecipeUserFavourite()
break
default: default:
break break
} }

View File

@@ -20,8 +20,14 @@
<i class="fa-regular fa-thumbs-up"></i> <i class="fa-regular fa-thumbs-up"></i>
</el-button> </el-button>
</el-badge> </el-badge>
<el-button circle><i class="fa-regular fa-star"></i></el-button> <el-badge v-if="!isCurrentUser"
<!-- <el-button circle><i class="fa-regular fa-comment"></i></el-button>--> :value="foodStore.currentRecipe.favouriteList.length" :max="9">
<el-button v-if="!isCurrentUser"
:type="isFavourite ? 'danger' : 'default'"
circle @click="handleFavouriteClick()">
<i class="fa-regular fa-star"></i>
</el-button>
</el-badge>
</div> </div>
</section> </section>
</div> </div>
@@ -49,6 +55,8 @@ onMounted(() => {
}) })
const isLike = computed(() => foodStore.currentRecipe.likeList.includes(userStore.getUserId())) const isLike = computed(() => foodStore.currentRecipe.likeList.includes(userStore.getUserId()))
const isFavourite = computed(() => foodStore.currentRecipe.favouriteList.includes(userStore.getUserId()))
const isCurrentUser = computed(() => foodStore.currentRecipe.userId === userStore.getUserId())
const handleLikeClick = () => { const handleLikeClick = () => {
if (isLike.value) { if (isLike.value) {
@@ -57,6 +65,14 @@ const handleLikeClick = () => {
foodStore.addRecipeLike(foodStore.currentRecipe.id as number) foodStore.addRecipeLike(foodStore.currentRecipe.id as number)
} }
} }
const handleFavouriteClick = () => {
if (isFavourite.value) {
foodStore.deleteRecipeFavourite(foodStore.currentRecipe.id as number)
} else {
foodStore.addRecipeFavourite(foodStore.currentRecipe.id as number)
}
}
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -22,6 +22,13 @@
:colors="foodStore.rateColorList"/> :colors="foodStore.rateColorList"/>
</el-form-item> </el-form-item>
<el-form-item label="共享">
<el-radio-group v-model="foodStore.currentRecipe.isShare">
<el-radio :value="false"></el-radio>
<el-radio :value="true"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="食材"> <el-form-item label="食材">
<food-material-form /> <food-material-form />
</el-form-item> </el-form-item>