feat:增加美食朋友圈接口

This commit is contained in:
2025-06-25 16:27:42 +08:00
parent 71c107f2e5
commit eff6078a98
17 changed files with 243 additions and 183 deletions

View File

@@ -1,41 +1,41 @@
import { cloudService } from './index' import { cloudService } from './index'
import type { IFoodQuery, IFoodRecipe, IFoodRecord, IFoodStats } from '@/types/food' import type { IFoodQuery, IRecipe, IRecord, IStats } from '@/types/food'
import type { IStatsChart } from 'vue3-common/types' import type { IStatsChart } from 'vue3-common/types'
export const addFoodRecipeApi = (foodRecipe: IFoodRecipe): Promise<boolean> => export const addRecipeApi = (recipe: IRecipe): Promise<boolean> =>
cloudService({ cloudService({
url: '/food-service/food/recipe', url: '/food-service/food/recipe',
method: 'post', method: 'post',
data: foodRecipe data: recipe
}) })
export const updateFoodRecipeApi = (id: number, foodRecipe: IFoodRecipe): Promise<boolean> => export const updateRecipeApi = (id: number, recipe: IRecipe): Promise<boolean> =>
cloudService({ cloudService({
url: `/food-service/food/recipe/${id}`, url: `/food-service/food/recipe/${id}`,
method: 'put', method: 'put',
data: foodRecipe data: recipe
}) })
export const deleteFoodRecipeApi = (id: number): Promise<boolean> => export const deleteRecipeApi = (id: number): Promise<boolean> =>
cloudService({ cloudService({
url: `/food-service/food/recipe/${id}`, url: `/food-service/food/recipe/${id}`,
method: 'DELETE' method: 'DELETE'
}) })
export const queryFoodRecipeByIdApi = (id: number): Promise<IFoodRecipe> => export const queryRecipeByIdApi = (id: number): Promise<IRecipe> =>
cloudService({ cloudService({
url: `/food-service/food/recipe/${id}`, url: `/food-service/food/recipe/${id}`,
method: 'get' method: 'get'
}) })
export const queryFoodRecipeApi = (query: IFoodQuery): Promise<IFoodRecipe[]> => export const queryRecipeApi = (query: IFoodQuery): Promise<IRecipe[]> =>
cloudService({ cloudService({
url: '/food-service/food/recipe', url: '/food-service/food/recipe',
method: 'get', method: 'get',
params: { ...query } params: { ...query }
}) })
export const queryFoodRecipeByPageApi = (currentPage: number, pageSize: number, export const queryRecipeByPageApi = (currentPage: number, pageSize: number,
query: IFoodQuery): Promise<any> => query: IFoodQuery): Promise<any> =>
cloudService({ cloudService({
url: '/food-service/food/recipe/page', url: '/food-service/food/recipe/page',
@@ -49,58 +49,58 @@ export const queryFoodNameListApi = (): Promise<string[]> =>
method: 'get' method: 'get'
}) })
export const addFoodRecordApi = (foodRecord: IFoodRecord): Promise<boolean> => export const addRecordApi = (record: IRecord): Promise<boolean> =>
cloudService({ cloudService({
url: '/food-service/food/record', url: '/food-service/food/record',
method: 'post', method: 'post',
data: foodRecord data: record
}) })
export const updateFoodRecordApi = (id: number, foodRecord: IFoodRecord): Promise<boolean> => export const updateRecordApi = (id: number, record: IRecord): Promise<boolean> =>
cloudService({ cloudService({
url: `/food-service/food/record/${id}`, url: `/food-service/food/record/${id}`,
method: 'put', method: 'put',
data: foodRecord data: record
}) })
export const deleteFoodRecordApi = (id: number): Promise<boolean> => export const deleteRecordApi = (id: number): Promise<boolean> =>
cloudService({ cloudService({
url: `/food-service/food/record/${id}`, url: `/food-service/food/record/${id}`,
method: 'DELETE' method: 'DELETE'
}) })
export const queryFoodRecordApi = (startDate: string, endDate: string): Promise<IFoodRecord[]> => export const queryRecordApi = (startDate: string, endDate: string): Promise<IRecord[]> =>
cloudService({ cloudService({
url: '/food-service/food/record', url: '/food-service/food/record',
method: 'get', method: 'get',
params: { startDate, endDate } params: { startDate, endDate }
}) })
export const queryFoodCategoryApi = (): Promise<string[]> => export const queryCategoryApi = (): Promise<string[]> =>
cloudService({ cloudService({
url: '/food-service/food/category', url: '/food-service/food/category',
method: 'get' method: 'get'
}) })
export const queryFoodStatsApi = (): Promise<IFoodStats> => export const queryStatsApi = (): Promise<IStats> =>
cloudService({ cloudService({
url: '/food-service/food/stats', url: '/food-service/food/stats',
method: 'get' method: 'get'
}) })
export const queryFoodRecordStatsApi = (): Promise<IStatsChart[]> => export const queryRecordStatsApi = (): Promise<IStatsChart[]> =>
cloudService({ cloudService({
url: '/food-service/food/stats/record', url: '/food-service/food/stats/record',
method: 'get' method: 'get'
}) })
export const queryFoodCategoryStatsApi = (): Promise<IStatsChart[]> => export const queryCategoryStatsApi = (): Promise<IStatsChart[]> =>
cloudService({ cloudService({
url: '/food-service/food/stats/category', url: '/food-service/food/stats/category',
method: 'get' method: 'get'
}) })
export const queryFoodRankStatsApi = (): Promise<IStatsChart[]> => export const queryRankStatsApi = (): Promise<IStatsChart[]> =>
cloudService({ cloudService({
url: '/food-service/food/stats/rank', url: '/food-service/food/stats/rank',
method: 'get' method: 'get'

23
src/apis/moment.ts Normal file
View File

@@ -0,0 +1,23 @@
import { cloudService } from './index'
import type { IComment, IMoment } from '@/types/food'
export const addCommentApi = (id: number, comment: IComment): Promise<boolean> =>
cloudService({
url: `/food-service/moment/${id}/comment`,
method: 'post',
data: comment
})
export const addMomentApi = (moment: IMoment): Promise<boolean> =>
cloudService({
url: '/food-service/moment',
method: 'post',
data: moment
})
export const queryMomentApi = (): Promise<IMoment[]> =>
cloudService({
url: '/food-service/moment',
method: 'get'
})

View File

@@ -2,7 +2,7 @@
<div class="life-record-card card-item"> <div class="life-record-card card-item">
<el-carousel v-if="props.foodItem?.recordList.length > 0" <el-carousel v-if="props.foodItem?.recordList.length > 0"
arrow="always" @change="changeFoodEvent"> arrow="always" @change="changeFoodEvent">
<el-carousel-item v-for="(item, index) in getFoodRecord(props.foodItem?.recordList)" <el-carousel-item v-for="(item, index) in getRecord(props.foodItem?.recordList)"
:key="index"> :key="index">
<el-image :src="serviceFileRootPath + item.imageUrl" <el-image :src="serviceFileRootPath + item.imageUrl"
fit="contain" alt="暂无成果"> fit="contain" alt="暂无成果">
@@ -27,11 +27,11 @@
<div class="button-info"> <div class="button-info">
<el-button type="primary" :icon="Edit" circle :size="'small'" <el-button type="primary" :icon="Edit" circle :size="'small'"
@click="editFoodRecipeClick"/> @click="editClick"/>
<el-button type="primary" :icon="MoreFilled" circle :size="'small'" <el-button type="primary" :icon="MoreFilled" circle :size="'small'"
@click="viewFoodRecipeClick()" /> @click="viewClick()" />
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'" <el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
@click="deleteFoodRecipeClick()" /> @click="deleteClick()" />
</div> </div>
</div> </div>
@@ -63,7 +63,7 @@ import { Edit, MoreFilled, DeleteFilled } from '@element-plus/icons-vue'
import { useFoodStore } from '@/store/food' import { useFoodStore } from '@/store/food'
import { defineProps, onMounted, reactive } from 'vue' import { defineProps, onMounted, reactive } from 'vue'
import type { PropType } from 'vue' import type { PropType } from 'vue'
import type { IFoodRecipe, IFoodRecord } from '@/types/food.ts' import type { IRecipe, IRecord } from '@/types/food.ts'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { formatDate } from 'vue3-common/utils/dateUtil' import { formatDate } from 'vue3-common/utils/dateUtil'
import { deepCopyObject } from 'vue3-common/utils/dataUtil' import { deepCopyObject } from 'vue3-common/utils/dataUtil'
@@ -81,7 +81,7 @@ const foodCardInfo = reactive({
const props = defineProps({ const props = defineProps({
foodItem: { foodItem: {
required: true, required: true,
type: Object as PropType<IFoodRecipe> type: Object as PropType<IRecipe>
} }
}) })
@@ -89,14 +89,14 @@ onMounted(() => {
foodCardInfo.recommendRate = props.foodItem?.recommendRate as number foodCardInfo.recommendRate = props.foodItem?.recommendRate as number
}) })
const getFoodRecord = (recordList: IFoodRecord[]) => { const getRecord = (recordList: IRecord[]) => {
return recordList.filter((item) => item.imageUrl) return recordList.filter((item) => item.imageUrl)
} }
const editFoodRecipeClick = async () => { const editClick = async () => {
const id = props.foodItem?.id const id = props.foodItem?.id
foodStore.foodRecipeApiType = 'UPDATE' foodStore.recipeApiType = 'UPDATE'
await foodStore.queryFoodRecipe(id as number) await foodStore.queryRecipe(id as number)
// foodStore.foodRecipeDialog = { // foodStore.foodRecipeDialog = {
// title: '编辑菜谱', // title: '编辑菜谱',
// visible: true, // visible: true,
@@ -104,14 +104,14 @@ const editFoodRecipeClick = async () => {
// } // }
} }
const viewFoodRecipeClick = async () => { const viewClick = async () => {
await router.push({ name: 'MobileHomeFoodDetailId', params: { id: props.foodItem?.id } }) await router.push({ name: 'MobileHomeFoodDetailId', params: { id: props.foodItem?.id } })
} }
const deleteFoodRecipeClick = () => { const deleteClick = () => {
foodStore.foodRecipeApiType = 'DELETE' foodStore.recipeApiType = 'DELETE'
commonElMessageBox(`请确认是否删除该菜谱: ${props.foodItem?.name}?`).then(() => { commonElMessageBox(`请确认是否删除该菜谱: ${props.foodItem?.name}?`).then(() => {
foodStore.handleFoodRecipeApi(props.foodItem) foodStore.handleRecipeApi(props.foodItem)
}) })
} }

View File

@@ -45,13 +45,13 @@ const updateInfo = () => {
const getCalendarMoveDate = () => { const getCalendarMoveDate = () => {
dailyInfo.calendarDate = formatDate(new Date()) dailyInfo.calendarDate = formatDate(new Date())
const foodLength = foodStore.foodRecordList.length const foodLength = foodStore.recordList.length
if (foodLength > 0) { if (foodLength > 0) {
const today = formatDate(new Date()) const today = formatDate(new Date())
if (foodStore.foodRecordList.some((item) => item.date === today)) { if (foodStore.recordList.some((item) => item.date === today)) {
dailyInfo.calendarDate = today dailyInfo.calendarDate = today
} else { } else {
dailyInfo.calendarDate = formatDate(foodStore.foodRecordList[foodLength - 1].date) dailyInfo.calendarDate = formatDate(foodStore.recordList[foodLength - 1].date)
} }
} else { } else {
dailyInfo.calendarDate = '' dailyInfo.calendarDate = ''
@@ -66,7 +66,7 @@ const getCalendarAttrDates = () => {
dates: new Date() dates: new Date()
}) })
const dateList = foodStore.foodRecordList.map((item) => item.date) const dateList = foodStore.recordList.map((item) => item.date)
dailyInfo.attrs.push({ dailyInfo.attrs.push({
dot: 'red', dot: 'red',
dates: [...new Set(dateList)] dates: [...new Set(dateList)]

View File

@@ -28,7 +28,7 @@ import { useFoodStore } from '@/store/food.ts'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useAppStore } from '@/store/app.ts' import { useAppStore } from '@/store/app.ts'
import { formatChineseDate } from 'vue3-common/utils/dateUtil' import { formatChineseDate } from 'vue3-common/utils/dateUtil'
import type { IFoodRecord } from '@/types/food.ts' import type { IRecord } from '@/types/food.ts'
const foodStore = useFoodStore() const foodStore = useFoodStore()
const appStore = useAppStore() const appStore = useAppStore()
@@ -55,7 +55,7 @@ onMounted(() => {
const dailyItemInfo = reactive({ const dailyItemInfo = reactive({
date: '', date: '',
itemList: [] as IFoodRecord[] itemList: [] as IRecord[]
}) })
/** /**
@@ -67,7 +67,7 @@ const getFoodDailyByDate = (date: string) => {
dailyItemInfo.date = formatChineseDate(date, 'MM月DD日 dddd') dailyItemInfo.date = formatChineseDate(date, 'MM月DD日 dddd')
dailyItemInfo.itemList = [] dailyItemInfo.itemList = []
foodStore.foodRecordList.forEach((item) => { foodStore.recordList.forEach((item) => {
if (item.date === date) { if (item.date === date) {
dailyItemInfo.itemList.push(item) dailyItemInfo.itemList.push(item)
} }
@@ -79,9 +79,9 @@ const getFoodDailyByDate = (date: string) => {
/** /**
* 点击美食记录按钮 * 点击美食记录按钮
* @param foodRecord 美食记录 * @param record 美食记录
*/ */
const selectFoodRecordClick = async (foodRecord: IFoodRecord) => { const selectFoodRecordClick = async (record: IRecord) => {
// foodStore.foodRecordApiType = 'UPDATE' // foodStore.foodRecordApiType = 'UPDATE'
// appStore.isShowMobileBack = true // appStore.isShowMobileBack = true

View File

@@ -3,7 +3,7 @@
<div class="button-container"> <div class="button-container">
<el-button type="primary" @click="addClick">新增</el-button> <el-button type="primary" @click="addClick">新增</el-button>
</div> </div>
<el-table :data="foodStore.currentFoodRecipe.materialList" <el-table :data="foodStore.currentRecipe.materialList"
border stripe class="material-table"> border stripe class="material-table">
<el-table-column type="index" align="center" width="40" /> <el-table-column type="index" align="center" width="40" />
<el-table-column prop="type" label="类型" align="center" min-width="10%"/> <el-table-column prop="type" label="类型" align="center" min-width="10%"/>
@@ -15,35 +15,35 @@
<script setup lang="ts"> <script setup lang="ts">
import { useFoodStore } from '@/store/food' import { useFoodStore } from '@/store/food'
import { IFoodMaterial } from '@/types/food' import { IMaterial } from '@/types/food'
import { deepCopyObject } from 'vue3-common/utils/dataUtil' import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { commonElMessageBox } from 'vue3-common/utils/elUtil' import { commonElMessageBox } from 'vue3-common/utils/elUtil'
const foodStore = useFoodStore() const foodStore = useFoodStore()
const addClick = () => { const addClick = () => {
foodStore.foodMaterialApiType = 'ADD' foodStore.materialApiType = 'ADD'
foodStore.foodMaterialDialog = { foodStore.materialDialog = {
title: '新增食材', title: '新增食材',
visible: true, visible: true,
data: foodStore.getEmptyFoodMaterial() data: foodStore.getEmptyMaterial()
} }
} }
const handleEditClick = (index: number, material: IFoodMaterial) => { const editClick = (index: number, material: IMaterial) => {
foodStore.editFoodMaterialIndex = index foodStore.editMaterialIndex = index
foodStore.foodMaterialApiType = 'UPDATE' foodStore.materialApiType = 'UPDATE'
foodStore.foodMaterialDialog = { foodStore.materialDialog = {
title: '编辑食材', title: '编辑食材',
visible: true, visible: true,
data: deepCopyObject(material) data: deepCopyObject(material)
} }
} }
const handleDeleteClick = (index: number, material: IFoodMaterial) => { const deleteClick = (index: number, material: IMaterial) => {
foodStore.foodMaterialApiType = 'DELETE' foodStore.materialApiType = 'DELETE'
commonElMessageBox(`确认删除该食材: ${material.name}?`).then(() => { commonElMessageBox(`确认删除该食材: ${material.name}?`).then(() => {
foodStore.currentFoodRecipe.materialList.splice(index, 1) foodStore.currentRecipe.materialList.splice(index, 1)
}) })
} }
</script> </script>

View File

@@ -2,16 +2,16 @@
<div ref="foodRecipeRef" class="food-recipe"> <div ref="foodRecipeRef" class="food-recipe">
<div class="query-item card-item"> <div class="query-item card-item">
<span>分类</span> <span>分类</span>
<el-segmented v-model="foodStore.queryRecipe.category" <el-segmented v-model="foodStore.queryRecipeSegment.category"
:options="queryCategoryList" :options="queryCategoryList"
@change="queryFoodSegmented"/> @change="queryFoodSegmented"/>
</div> </div>
<div class="food-list"> <div class="food-list">
<el-empty v-if="foodStore.foodRecipeList.length === 0" description="暂无美食记录"/> <el-empty v-if="foodStore.recipeList.length === 0" description="暂无美食记录"/>
<div v-else class="list"> <div v-else class="list">
<food-card v-for="(item, index) in foodStore.foodRecipeList" <food-card v-for="(item, index) in foodStore.recipeList"
:key="index" :food-item="item"/> :key="index" :food-item="item"/>
</div> </div>
</div> </div>
@@ -33,7 +33,7 @@ const foodStore = useFoodStore()
const queryCategoryList = ref([] as ICategory[]) const queryCategoryList = ref([] as ICategory[])
onMounted(async () => { onMounted(async () => {
await foodStore.queryFoodCategory() await foodStore.queryCategory()
initCategoryList() initCategoryList()
// foodStore.foodRecipeList = [] // foodStore.foodRecipeList = []
await foodStore.refreshInfo() await foodStore.refreshInfo()
@@ -50,7 +50,7 @@ const initCategoryList = () => {
label: '全部', label: '全部',
value: '' value: ''
}) })
foodStore.foodCategoryList.forEach((item) => { foodStore.categoryList.forEach((item) => {
queryCategoryList.value.push({ queryCategoryList.value.push({
label: item, label: item,
value: item value: item
@@ -60,8 +60,8 @@ const initCategoryList = () => {
const queryFoodSegmented = async () => { const queryFoodSegmented = async () => {
// foodStore.pageInfo.currentPage = 1 // foodStore.pageInfo.currentPage = 1
foodStore.foodRecipeList = [] foodStore.recipeList = []
await foodStore.queryFoodRecipeList() await foodStore.queryRecipeList()
} }
const handleScroll = () => { const handleScroll = () => {
@@ -71,7 +71,7 @@ const handleScroll = () => {
foodStore.isScrollEnd = pageSize * currentPage > totalSize foodStore.isScrollEnd = pageSize * currentPage > totalSize
if (!foodStore.isScrollEnd) { if (!foodStore.isScrollEnd) {
foodStore.pageInfo.currentPage++ foodStore.pageInfo.currentPage++
foodStore.queryFoodRecipeList() foodStore.queryRecipeList()
} }
} }
} }

View File

@@ -4,7 +4,7 @@
<el-button type="primary" @click="addClick">新增</el-button> <el-button type="primary" @click="addClick">新增</el-button>
</div> </div>
<el-table :data="foodStore.currentFoodRecipe.stepList" <el-table :data="foodStore.currentRecipe.stepList"
border class="food-step-table"> border class="food-step-table">
<el-table-column type="index" align="center" width="40" /> <el-table-column type="index" align="center" width="40" />
<el-table-column prop="content" label="内容" align="center" min-width="20%" /> <el-table-column prop="content" label="内容" align="center" min-width="20%" />
@@ -20,7 +20,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useFoodStore } from '@/store/food' import { useFoodStore } from '@/store/food'
import { IFoodStep } from '@/types/food' import { IStep } from '@/types/food'
import { deepCopyObject } from 'vue3-common/utils/dataUtil' import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { commonElMessageBox } from 'vue3-common/utils/elUtil' import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { serviceFileRootPath } from '@/utils' import { serviceFileRootPath } from '@/utils'
@@ -28,30 +28,30 @@ import { serviceFileRootPath } from '@/utils'
const foodStore = useFoodStore() const foodStore = useFoodStore()
const addClick = () => { const addClick = () => {
foodStore.foodStepApiType = 'ADD' foodStore.stepApiType = 'ADD'
// foodStore.foodStepImageList = [] // foodStore.foodStepImageList = []
foodStore.foodStepDialog = { foodStore.stepDialog = {
title: '新增步骤', title: '新增步骤',
visible: true, visible: true,
data: foodStore.getEmptyFoodStep() data: foodStore.getEmptyStep()
} }
} }
const handleEditClick = (index: number, step: IFoodStep) => { const editClick = (index: number, step: IStep) => {
foodStore.editFoodStepIndex = index foodStore.editStepIndex = index
foodStore.foodStepApiType = 'UPDATE' foodStore.stepApiType = 'UPDATE'
foodStore.foodStepDialog = { foodStore.stepDialog = {
title: '编辑步骤', title: '编辑步骤',
visible: true, visible: true,
data: deepCopyObject(step) data: deepCopyObject(step)
} }
} }
const handleDeleteClick = (index: number) => { const deleteClick = (index: number) => {
commonElMessageBox('确认删除该步骤?').then(() => { commonElMessageBox('确认删除该步骤?').then(() => {
foodStore.foodStepApiType = 'DELETE' foodStore.stepApiType = 'DELETE'
foodStore.currentFoodRecipe.stepList.splice(index, 1) foodStore.currentRecipe.stepList.splice(index, 1)
foodStore.currentFoodRecipe.stepList.forEach((item) => { foodStore.currentRecipe.stepList.forEach((item) => {
if (item.sort > index) { if (item.sort > index) {
item.sort -= 1 item.sort -= 1
} }

View File

@@ -7,8 +7,8 @@
@change="changeDateEvent" @change="changeDateEvent"
/> />
<el-timeline v-if="foodStore.foodRecordList.length > 0"> <el-timeline v-if="foodStore.recordList.length > 0">
<el-timeline-item v-for="(item, index) in foodStore.foodRecordList" <el-timeline-item v-for="(item, index) in foodStore.recordList"
:key="index" :timestamp="formatDate(item.date)" placement="top"> :key="index" :timestamp="formatDate(item.date)" placement="top">
<div class="timeline-info card-item"> <div class="timeline-info card-item">
<span>{{ item.name }}</span> <span>{{ item.name }}</span>

View File

@@ -1,15 +1,15 @@
<template> <template>
<div class="moment-card card-item"> <div class="moment-card card-item">
<el-avatar src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" /> <el-avatar :src="serviceFileRootPath + props.item.avatar" fit="contain"/>
<div class="moment-content"> <div class="moment-content">
<span class="username">Cxx0822</span> <span class="username">{{ props.item.username }}</span>
<p>今天烧了个红烧鱼 非常不错</p> <p>{{ props.item.content }}</p>
<el-image style="width: 200px; height: 200px" <el-image style="width: 200px; height: 200px"
src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" fit="fill" /> src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" fit="fill" />
<p>1小时前</p> <p>{{ props.item.date }}</p>
<div class="button-container"> <div class="button-container">
<el-button circle size="small"> <el-button circle size="small">
@@ -49,14 +49,10 @@
</div> </div>
<div class="moment-comment-list"> <div class="moment-comment-list">
<div class="moment-comment-item"> <div v-for="(comment, index) in props.item.commentList" :key="index"
<span class="name">Cxx</span> class="moment-comment-item">
<span>确实很不错</span> <span class="name">{{ comment.username }}</span>
</div> <span>{{ comment.content }}</span>
<div class="moment-comment-item">
<span class="name">Cxx</span>
<span>确实很不错阿</span>
</div> </div>
</div> </div>
</div> </div>
@@ -67,7 +63,16 @@
import EmojiPicker from 'vue3-emoji-picker' import EmojiPicker from 'vue3-emoji-picker'
import 'vue3-emoji-picker/css' import 'vue3-emoji-picker/css'
import { Edit } from '@element-plus/icons-vue' import { Edit } from '@element-plus/icons-vue'
import { reactive } from 'vue' import { defineProps, PropType, reactive } from 'vue'
import { IMoment } from '@/types/food'
import { serviceFileRootPath } from '@/utils'
const props = defineProps({
item: {
required: true,
type: Object as PropType<IMoment>
}
})
const momentInfo = reactive({ const momentInfo = reactive({
isShowComment: false, isShowComment: false,

View File

@@ -54,12 +54,12 @@ const updateChart = () => {
const MAX_COUNT = 5 const MAX_COUNT = 5
// 记录统计图 // 记录统计图
const dailyXAxis = foodStore.foodRecordStats.slice(0, MAX_COUNT).map((item) => item.name) const dailyXAxis = foodStore.recordStats.slice(0, MAX_COUNT).map((item) => item.name)
const dailyYAxis = foodStore.foodRecordStats.slice(0, MAX_COUNT).map((item) => item.value) const dailyYAxis = foodStore.recordStats.slice(0, MAX_COUNT).map((item) => item.value)
foodDailyChart.setOption(lineChartOptions('日期', dailyXAxis, '次数(次)', dailyYAxis)) foodDailyChart.setOption(lineChartOptions('日期', dailyXAxis, '次数(次)', dailyYAxis))
// 类别统计图 // 类别统计图
const foodCategoryData = foodStore.foodCategoryStats.slice(0, MAX_COUNT) const foodCategoryData = foodStore.categoryStats.slice(0, MAX_COUNT)
foodCategoryChart.setOption(pieChartOptions('category', '个', foodCategoryData)) foodCategoryChart.setOption(pieChartOptions('category', '个', foodCategoryData))
} }
</script> </script>

View File

@@ -15,5 +15,5 @@ const foodStore = useFoodStore()
const MAX_RANK_COUNT = 10 const MAX_RANK_COUNT = 10
const foodRankList = computed(() => foodStore.foodRankStats) const foodRankList = computed(() => foodStore.rankStats)
</script> </script>

View File

@@ -2,15 +2,15 @@
<div class="common-statistics"> <div class="common-statistics">
<stats-card <stats-card
title="菜谱总数" color="#CA6C65" unit="个" icon="iconfont icon-caipu" title="菜谱总数" color="#CA6C65" unit="个" icon="iconfont icon-caipu"
:value="foodStore.foodStats.recipeCount"/> :value="foodStore.stats.recipeCount"/>
<stats-card <stats-card
title="菜谱类别" color="#6FBB69" unit="种" icon="iconfont icon-leibie" title="菜谱类别" color="#6FBB69" unit="种" icon="iconfont icon-leibie"
:value="foodStore.foodStats.categoryCount"/> :value="foodStore.stats.categoryCount"/>
<stats-card <stats-card
title="做菜次数" color="#E4BF62" unit="次" icon="iconfont icon-cishu" title="做菜次数" color="#E4BF62" unit="次" icon="iconfont icon-cishu"
:value="foodStore.foodStats.workCount"/> :value="foodStore.stats.workCount"/>
<stats-card <stats-card
title="平均次数" color="#5470C6" unit="次/月" icon="iconfont icon-pinlv" title="平均次数" color="#5470C6" unit="次/月" icon="iconfont icon-pinlv"
@@ -26,17 +26,17 @@ import { useFoodStore } from '@/store/food'
const foodStore = useFoodStore() const foodStore = useFoodStore()
onMounted(async () => { onMounted(async () => {
await foodStore.queryFoodStats() await foodStore.queryStats()
}) })
const getAverageCount = () => { const getAverageCount = () => {
if (foodStore.queryDateType === 'month') { if (foodStore.queryDateType === 'month') {
return foodStore.foodRecordList.length return foodStore.recordList.length
} else { } else {
const result = {} const result = {}
// 遍历数据,按月统计出现次数 // 遍历数据,按月统计出现次数
foodStore.foodRecordList.forEach((item) => { foodStore.recordList.forEach((item) => {
const date = new Date(item.date) const date = new Date(item.date)
const month = String(date.getMonth() + 1) const month = String(date.getMonth() + 1)
@@ -47,7 +47,7 @@ const getAverageCount = () => {
} }
}) })
return foodStore.foodRecordList.length / Object.keys(result).length return foodStore.recordList.length / Object.keys(result).length
} }
} }
</script> </script>

View File

@@ -1,31 +1,32 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import type { import type {
IFoodRecipe, IFoodMaterial, IFoodStep, IRecipe, IMaterial, IStep,
IFoodStats, IFoodRecord IStats, IRecord, IMoment
} from '@/types/food' } from '@/types/food'
import { import {
addFoodRecipeApi, addRecipeApi,
addFoodRecordApi, addRecordApi,
deleteFoodRecipeApi, deleteRecipeApi,
deleteFoodRecordApi, deleteRecordApi,
queryFoodNameListApi, queryFoodNameListApi,
queryFoodRecipeByIdApi, queryRecipeByIdApi,
queryFoodRecordApi, queryRecordApi,
queryFoodStatsApi, queryStatsApi,
updateFoodRecipeApi, updateRecipeApi,
updateFoodRecordApi, updateRecordApi,
queryFoodCategoryApi, queryCategoryApi,
queryFoodRecordStatsApi, queryRecordStatsApi,
queryFoodCategoryStatsApi, queryFoodRankStatsApi, queryFoodRecipeApi queryCategoryStatsApi, queryRankStatsApi, queryRecipeApi
} 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'
import { getCurrentMonth, getCurrentYear, getStartEndDate } from 'vue3-common/utils/dateUtil' import { getCurrentMonth, getCurrentYear, getStartEndDate } from 'vue3-common/utils/dateUtil'
import { queryMomentApi } from '@/apis/moment'
export const useFoodStore = defineStore('food', { export const useFoodStore = defineStore('food', {
state: () => ({ state: () => ({
foodRecipeList: [] as IFoodRecipe[], recipeList: [] as IRecipe[],
currentFoodRecipe: { currentRecipe: {
id: 0, id: 0,
name: '', name: '',
category: '', category: '',
@@ -34,18 +35,19 @@ export const useFoodStore = defineStore('food', {
materialList: [], materialList: [],
stepList: [], stepList: [],
recordList: [] recordList: []
} as IFoodRecipe, } as IRecipe,
foodRecordList: [] as IFoodRecord[], recordList: [] as IRecord[],
currentFoodRecord: { currentRecord: {
id: 0, id: 0,
name: '', name: '',
category: '', category: '',
date: '', date: '',
person: '', person: '',
imageUrl: '' imageUrl: ''
} as IFoodRecord, } as IRecord,
momentList: [] as IMoment[],
foodNameList: [] as string[], foodNameList: [] as string[],
queryRecipe: { queryRecipeSegment: {
category: '' category: ''
}, },
queryRecord: { queryRecord: {
@@ -54,24 +56,24 @@ export const useFoodStore = defineStore('food', {
}, },
queryDateType: 'month' as 'month' | 'year', queryDateType: 'month' as 'month' | 'year',
queryDateList: [] as string[], queryDateList: [] as string[],
foodRecipeApiType: 'ADD' as IHandleApi, recipeApiType: 'ADD' as IHandleApi,
foodMaterialApiType: 'ADD' as IHandleApi, materialApiType: 'ADD' as IHandleApi,
foodMaterialDialog: { materialDialog: {
visible: false, visible: false,
title: '', title: '',
data: {} data: {}
} as IDialog<IFoodMaterial>, } as IDialog<IMaterial>,
editFoodMaterialIndex: 0, editMaterialIndex: 0,
foodStepApiType: 'ADD' as IHandleApi, stepApiType: 'ADD' as IHandleApi,
foodStepDialog: { stepDialog: {
visible: false, visible: false,
title: '', title: '',
data: {} data: {}
} as IDialog<IFoodStep>, } as IDialog<IStep>,
editFoodStepIndex: 0, editStepIndex: 0,
foodRecordApiType: 'ADD' as IHandleApi, recordApiType: 'ADD' as IHandleApi,
editFoodRecordIndex: 0, editRecordIndex: 0,
foodImagePath: 'food', imagePath: 'food',
rateColorList: ['#E2DEDC', '#F7BA2A', '#009FA8'], rateColorList: ['#E2DEDC', '#F7BA2A', '#009FA8'],
pageInfo: { pageInfo: {
currentPage: 1, currentPage: 1,
@@ -79,59 +81,62 @@ export const useFoodStore = defineStore('food', {
totalSize: 1 totalSize: 1
}, },
isScrollEnd: false, isScrollEnd: false,
foodCategoryList: [] as string[], categoryList: [] as string[],
foodStats: { stats: {
recipeCount: 0, recipeCount: 0,
categoryCount: 0, categoryCount: 0,
workCount: 0 workCount: 0
} as IFoodStats, } as IStats,
foodRecordStats: [] as IStatsChart[], recordStats: [] as IStatsChart[],
foodCategoryStats: [] as IStatsChart[], categoryStats: [] as IStatsChart[],
foodRankStats: [] as IStatsChart[], rankStats: [] as IStatsChart[],
isRefresh: false isRefresh: false
}), }),
actions: { actions: {
async queryFoodRecipeList() { async queryRecipeList() {
// const { currentPage, pageSize } = this.pageInfo // const { currentPage, pageSize } = this.pageInfo
// const result = await queryFoodRecipeByPageApi(currentPage, pageSize, this.queryRecipe) // const result = await queryFoodRecipeByPageApi(currentPage, pageSize, this.queryRecipe)
// this.foodRecipeList.push(...result.records) // this.foodRecipeList.push(...result.records)
// this.pageInfo.totalSize = result.total // this.pageInfo.totalSize = result.total
this.foodRecipeList = await queryFoodRecipeApi(this.queryRecipe) this.recipeList = await queryRecipeApi(this.queryRecipe)
}, },
async queryFoodRecordList() { 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
this.queryDateList = getStartEndDate(date, this.queryDateType) this.queryDateList = getStartEndDate(date, this.queryDateType)
this.foodRecordList = await queryFoodRecordApi(this.queryDateList[0], this.queryDateList[1]) this.recordList = await queryRecordApi(this.queryDateList[0], this.queryDateList[1])
},
async queryMomentList() {
this.momentList = await queryMomentApi()
}, },
async queryFoodNameList() { async queryFoodNameList() {
this.foodNameList = await queryFoodNameListApi() this.foodNameList = await queryFoodNameListApi()
}, },
async queryFoodRecipe(id: number) { async queryRecipe(id: number) {
this.currentFoodRecipe = await queryFoodRecipeByIdApi(id) this.currentRecipe = await queryRecipeByIdApi(id)
}, },
async queryFoodStats() { async queryStats() {
this.foodStats = await queryFoodStatsApi() this.stats = await queryStatsApi()
this.foodRecordStats = await queryFoodRecordStatsApi() this.recordStats = await queryRecordStatsApi()
this.foodCategoryStats = await queryFoodCategoryStatsApi() this.categoryStats = await queryCategoryStatsApi()
this.foodRankStats = await queryFoodRankStatsApi() this.rankStats = await queryRankStatsApi()
this.isRefresh = !this.isRefresh this.isRefresh = !this.isRefresh
}, },
async queryFoodCategory() { async queryCategory() {
this.foodCategoryList = await queryFoodCategoryApi() this.categoryList = await queryCategoryApi()
}, },
async handleFoodRecipeApi(foodRecipe: IFoodRecipe) { async handleRecipeApi(recipe: IRecipe) {
switch (this.foodRecipeApiType) { switch (this.recipeApiType) {
case 'ADD': case 'ADD':
await addFoodRecipeApi(foodRecipe) await addRecipeApi(recipe)
ElMessage.success('新增菜谱成功') ElMessage.success('新增菜谱成功')
break break
case 'UPDATE': case 'UPDATE':
await updateFoodRecipeApi(foodRecipe.id as number, foodRecipe) await updateRecipeApi(recipe.id as number, recipe)
ElMessage.success('更新菜谱成功') ElMessage.success('更新菜谱成功')
break break
case 'DELETE': case 'DELETE':
await deleteFoodRecipeApi(foodRecipe.id as number) await deleteRecipeApi(recipe.id as number)
ElMessage.success('删除菜谱成功') ElMessage.success('删除菜谱成功')
break break
default: default:
@@ -139,18 +144,18 @@ export const useFoodStore = defineStore('food', {
} }
await this.refreshInfo() await this.refreshInfo()
}, },
async handleFoodRecordApi(foodRecord: IFoodRecord) { async handleRecordApi(record: IRecord) {
switch (this.foodRecordApiType) { switch (this.recordApiType) {
case 'ADD': case 'ADD':
await addFoodRecordApi(foodRecord) await addRecordApi(record)
ElMessage.success('新增成果成功') ElMessage.success('新增成果成功')
break break
case 'UPDATE': case 'UPDATE':
await updateFoodRecordApi(foodRecord.id as number, foodRecord) await updateRecordApi(record.id as number, record)
ElMessage.success('更新成果成功') ElMessage.success('更新成果成功')
break break
case 'DELETE': case 'DELETE':
await deleteFoodRecordApi(foodRecord.id as number) await deleteRecordApi(record.id as number)
ElMessage.success('删除成果成功') ElMessage.success('删除成果成功')
break break
default: default:
@@ -159,11 +164,11 @@ export const useFoodStore = defineStore('food', {
await this.refreshInfo() await this.refreshInfo()
}, },
async refreshInfo() { async refreshInfo() {
await this.queryFoodRecipeList() await this.queryRecipeList()
await this.queryFoodRecordList() await this.queryRecordList()
this.isRefresh = !this.isRefresh this.isRefresh = !this.isRefresh
}, },
getEmptyFoodRecipe(): IFoodRecipe { getEmptyRecipe(): IRecipe {
return { return {
category: '', category: '',
materialList: [], materialList: [],
@@ -174,22 +179,23 @@ export const useFoodStore = defineStore('food', {
recordList: [] recordList: []
} }
}, },
getEmptyFoodMaterial(): IFoodMaterial { getEmptyMaterial(): IMaterial {
return { return {
amount: '', amount: '',
name: '', name: '',
type: '' type: ''
} }
}, },
getEmptyFoodStep(): IFoodStep { getEmptyStep(): IStep {
return { return {
content: '', content: '',
imageUrl: '', imageUrl: '',
sort: 0 sort: 0
} }
}, },
getEmptyFoodRecord(): IFoodRecord { getEmptyRecord(): IRecord {
return { return {
person: 0,
category: '', category: '',
date: '', date: '',
id: 0, id: 0,

View File

@@ -3,7 +3,7 @@ export type IFoodSegment = 'recipe' | 'daily' | 'timeline'
/** /**
* 食材信息 * 食材信息
*/ */
export interface IFoodMaterial { export interface IMaterial {
type: string; type: string;
name: string; name: string;
amount: string; amount: string;
@@ -12,7 +12,7 @@ export interface IFoodMaterial {
/** /**
* 步骤信息 * 步骤信息
*/ */
export interface IFoodStep { export interface IStep {
sort: number; sort: number;
content: string; content: string;
imageUrl: string; imageUrl: string;
@@ -21,7 +21,7 @@ export interface IFoodStep {
/** /**
* 成果信息 * 成果信息
*/ */
export interface IFoodRecord { export interface IRecord {
id: number; id: number;
name: string; name: string;
category: string; category: string;
@@ -33,7 +33,7 @@ export interface IFoodRecord {
/** /**
* 菜谱信息 * 菜谱信息
*/ */
export interface IFoodRecipe { export interface IRecipe {
id?: number; id?: number;
/** /**
* 美食名称 * 美食名称
@@ -54,21 +54,21 @@ export interface IFoodRecipe {
/** /**
* 食材 * 食材
*/ */
materialList: IFoodMaterial[]; materialList: IMaterial[];
/** /**
* 步骤 * 步骤
*/ */
stepList: IFoodStep[]; stepList: IStep[];
/** /**
* 成果 * 成果
*/ */
recordList: IFoodRecord[]; recordList: IRecord[];
} }
/** /**
* 统计信息 * 统计信息
*/ */
export interface IFoodStats { export interface IStats {
recipeCount: number; recipeCount: number;
categoryCount: number; categoryCount: number;
workCount: number; workCount: number;
@@ -77,3 +77,20 @@ export interface IFoodStats {
export interface IFoodQuery { export interface IFoodQuery {
category: string; category: string;
} }
export interface IComment {
id: number;
username: string;
avatar: string;
content: string;
date: string;
}
export interface IMoment {
id: number;
username: string;
avatar: string;
content: string;
date: string;
commentList: IComment[];
}

View File

@@ -1,11 +1,20 @@
<template> <template>
<div class="common-mobile-view food-moment"> <div class="common-mobile-view food-moment">
<moment-card v-for="(item, index) in 4" :key="index"/> <moment-card v-for="(item, index) in foodStore.momentList"
:key="index" :item="item"/>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import MomentCard from '@/components/Moment/MomentCard.vue' import MomentCard from '@/components/Moment/MomentCard.vue'
import { useFoodStore } from '@/store/food'
import { onMounted } from 'vue'
const foodStore = useFoodStore()
onMounted(async () => {
await foodStore.queryMomentList()
})
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@@ -71,13 +71,13 @@ const addClick = () => {
switch (foodInfo.recordSegment) { switch (foodInfo.recordSegment) {
case 'recipe': case 'recipe':
foodStore.foodRecipeApiType = 'ADD' foodStore.recipeApiType = 'ADD'
foodStore.currentFoodRecipe = foodStore.getEmptyFoodRecipe() foodStore.currentRecipe = foodStore.getEmptyRecipe()
router.push('/record/recipeForm') router.push('/record/recipeForm')
break break
case 'daily': case 'daily':
foodStore.foodRecordApiType = 'ADD' foodStore.recordApiType = 'ADD'
foodStore.currentFoodRecord = foodStore.getEmptyFoodRecord() foodStore.currentRecord = foodStore.getEmptyRecord()
router.push('/record/recordForm') router.push('/record/recordForm')
break break
default: default: