feat:搭建基本框架
This commit is contained in:
129
src/components/Food/FoodCard.vue
Normal file
129
src/components/Food/FoodCard.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<div class="life-record-card card-item">
|
||||
<el-carousel v-if="props.foodItem?.recordList.length > 0"
|
||||
arrow="always" @change="changeFoodEvent">
|
||||
<el-carousel-item v-for="(item, index) in getFoodRecord(props.foodItem?.recordList)"
|
||||
:key="index">
|
||||
<el-image :src="serviceFileRootPath + item.imageUrl"
|
||||
fit="contain" alt="暂无成果">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<span>暂无图片</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
|
||||
<el-empty v-else description="暂无成果" />
|
||||
|
||||
<div class="record-info">
|
||||
<div class="top-container">
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<span class="title">{{ props.foodItem.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="button-info">
|
||||
<el-button type="primary" :icon="Edit" circle :size="'small'"
|
||||
@click="editFoodRecipeClick"/>
|
||||
<el-button type="primary" :icon="MoreFilled" circle :size="'small'"
|
||||
@click="viewFoodRecipeClick()" />
|
||||
<el-button type="danger" :icon="DeleteFilled" circle :size="'small'"
|
||||
@click="deleteFoodRecipeClick()" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bottom-container">
|
||||
<div class="info-list">
|
||||
<div class="info-item category-item">
|
||||
<i class="fa-solid fa-utensils"></i>
|
||||
{{ props.foodItem.category }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="props.foodItem?.recordList.length > 0" class="info-list">
|
||||
<div class="info-item">
|
||||
<i class="fa-solid fa-flag"></i>
|
||||
{{ props.foodItem.recordList[foodCardInfo.currentFoodIndex].person}}
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<i class="fa-regular fa-calendar-check"></i>
|
||||
{{ formatDate(props.foodItem.recordList[foodCardInfo.currentFoodIndex].date) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<span>暂无成果</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Edit, MoreFilled, DeleteFilled } from '@element-plus/icons-vue'
|
||||
import { useFoodStore } from '@/store/food'
|
||||
import { defineProps, onMounted, PropType, reactive } from 'vue'
|
||||
import { IFoodRecipe, IFoodRecord } from '@/types/food.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { formatDate } from 'vue3-common/utils/dateUtil'
|
||||
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
|
||||
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
|
||||
import { serviceFileRootPath } from '@/utils'
|
||||
import { isMobile } from 'vue3-common/utils/layoutUtil'
|
||||
|
||||
const router = useRouter()
|
||||
const foodStore = useFoodStore()
|
||||
|
||||
const foodCardInfo = reactive({
|
||||
recommendRate: 0,
|
||||
currentFoodIndex: 0
|
||||
})
|
||||
|
||||
const props = defineProps({
|
||||
foodItem: {
|
||||
required: true,
|
||||
type: Object as PropType<IFoodRecipe>
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
foodCardInfo.recommendRate = props.foodItem?.recommendRate as number
|
||||
})
|
||||
|
||||
const getFoodRecord = (recordList: IFoodRecord[]) => {
|
||||
return recordList.filter((item) => item.imageUrl)
|
||||
}
|
||||
|
||||
const editFoodRecipeClick = async () => {
|
||||
const id = props.foodItem?.id
|
||||
foodStore.foodRecipeApiType = 'UPDATE'
|
||||
await foodStore.queryFoodRecipe(id as number)
|
||||
// foodStore.foodRecipeDialog = {
|
||||
// title: '编辑菜谱',
|
||||
// visible: true,
|
||||
// data: deepCopyObject(foodStore.currentFoodRecipe)
|
||||
// }
|
||||
}
|
||||
|
||||
const viewFoodRecipeClick = async () => {
|
||||
await router.push({ name: 'MobileHomeFoodDetailId', params: { id: props.foodItem?.id } })
|
||||
}
|
||||
|
||||
const deleteFoodRecipeClick = () => {
|
||||
foodStore.foodRecipeApiType = 'DELETE'
|
||||
commonElMessageBox(`请确认是否删除该菜谱: ${props.foodItem?.name}?`).then(() => {
|
||||
foodStore.handleFoodRecipeApi(props.foodItem?.id as number)
|
||||
})
|
||||
}
|
||||
|
||||
const changeFoodEvent = (current: number) => {
|
||||
foodCardInfo.currentFoodIndex = current
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use "@/styles/life.record.module.scss";
|
||||
</style>
|
||||
114
src/components/Food/FoodDaily.vue
Normal file
114
src/components/Food/FoodDaily.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div class="food-daily">
|
||||
<v-calendar ref="calendarRef" :attributes='dailyInfo.attrs'
|
||||
@dayclick="dateClick" @did-move="changeMonthClick"
|
||||
:is-dark="appStore.isDark"
|
||||
class="food-calendar card-item"/>
|
||||
|
||||
<food-daily-item :date="dailyInfo.calendarDate"/>
|
||||
|
||||
<draggable-button @click="addClick"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FoodDailyItem from '@/components/Food/FoodDailyItem.vue'
|
||||
import DraggableButton from '@/components/DraggableButton.vue'
|
||||
import { reactive, watch, ref, onMounted } from 'vue'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { formatDate } from 'vue3-common/utils/dateUtil'
|
||||
import { useAppStore } from '@/store/app'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const calendarRef = ref()
|
||||
const foodStore = useFoodStore()
|
||||
const appStore = useAppStore()
|
||||
const router = useRouter()
|
||||
|
||||
onMounted(() => {
|
||||
const { month } = foodStore.queryRecord
|
||||
calendarRef.value.move(new Date(month))
|
||||
})
|
||||
|
||||
watch(() => foodStore.isRefresh, () => {
|
||||
updateInfo()
|
||||
})
|
||||
|
||||
const dailyInfo = reactive({
|
||||
calendarDate: '',
|
||||
attrs: [] as any[]
|
||||
})
|
||||
|
||||
const addClick = () => {
|
||||
foodStore.foodRecordApiType = 'ADD'
|
||||
foodStore.currentFoodRecord = foodStore.getEmptyFoodRecord()
|
||||
router.push({ name: 'MobileHomeFoodRecordForm' })
|
||||
}
|
||||
|
||||
const updateInfo = () => {
|
||||
getCalendarMoveDate()
|
||||
getCalendarAttrDates()
|
||||
}
|
||||
|
||||
const getCalendarMoveDate = () => {
|
||||
dailyInfo.calendarDate = formatDate(new Date())
|
||||
|
||||
const foodLength = foodStore.foodRecordList.length
|
||||
if (foodLength > 0) {
|
||||
const today = formatDate(new Date())
|
||||
if (foodStore.foodRecordList.some((item) => item.date === today)) {
|
||||
dailyInfo.calendarDate = today
|
||||
} else {
|
||||
dailyInfo.calendarDate = formatDate(foodStore.foodRecordList[foodLength - 1].date)
|
||||
}
|
||||
} else {
|
||||
dailyInfo.calendarDate = ''
|
||||
}
|
||||
}
|
||||
|
||||
const getCalendarAttrDates = () => {
|
||||
dailyInfo.attrs = []
|
||||
dailyInfo.attrs.push({
|
||||
key: 'today',
|
||||
highlight: true,
|
||||
dates: new Date()
|
||||
})
|
||||
|
||||
const dateList = foodStore.foodRecordList.map((item) => item.date)
|
||||
dailyInfo.attrs.push({
|
||||
dot: 'red',
|
||||
dates: [...new Set(dateList)]
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择日期事件
|
||||
* @param day 日期
|
||||
*/
|
||||
const dateClick = (day) => {
|
||||
dailyInfo.calendarDate = formatDate(day.date)
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择月份事件
|
||||
*/
|
||||
const changeMonthClick = async (value) => {
|
||||
foodStore.queryRecord.month = value[0].id
|
||||
await foodStore.refreshInfo()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.food-daily {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1vh;
|
||||
|
||||
.food-calendar {
|
||||
width: 100%;
|
||||
justify-self: center;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
108
src/components/Food/FoodDailyItem.vue
Normal file
108
src/components/Food/FoodDailyItem.vue
Normal file
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<el-empty v-if="dailyItemInfo.itemList.length === 0" />
|
||||
|
||||
<div v-else class="food-daily-item card-item">
|
||||
<daily-summary :date="dailyItemInfo.date"/>
|
||||
|
||||
<div class="daily-list">
|
||||
<daily-card v-for="(item, index) in dailyItemInfo.itemList"
|
||||
:key="index" @click="selectFoodRecordClick(item)"
|
||||
:title="item.name" :content="item.category">
|
||||
<template #icon>
|
||||
<el-avatar>{{ item.category.charAt(0) }}</el-avatar>
|
||||
</template>
|
||||
|
||||
<template #extra>
|
||||
<span>{{ item.person }}</span>
|
||||
<span>{{ item.date }}</span>
|
||||
</template>
|
||||
</daily-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DailySummary from '@/components/DailySummary.vue'
|
||||
import DailyCard from '@/components/DailyCard.vue'
|
||||
import { defineProps, onMounted, reactive, watch } from 'vue'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAppStore } from '@/store/app.ts'
|
||||
import { formatChineseDate } from 'vue3-common/utils/dateUtil'
|
||||
import { IFoodRecord } from '@/types/food.ts'
|
||||
|
||||
const foodStore = useFoodStore()
|
||||
const appStore = useAppStore()
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps({
|
||||
date: {
|
||||
required: true,
|
||||
type: String
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.date, () => {
|
||||
getFoodDailyByDate(props.date as string)
|
||||
})
|
||||
|
||||
watch(() => foodStore.isRefresh, () => {
|
||||
getFoodDailyByDate(props.date as string)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
getFoodDailyByDate(props.date as string)
|
||||
})
|
||||
|
||||
const dailyItemInfo = reactive({
|
||||
date: '',
|
||||
itemList: [] as IFoodRecord[]
|
||||
})
|
||||
|
||||
/**
|
||||
* 根据日期获取当天的美食记录
|
||||
* @param date
|
||||
*/
|
||||
const getFoodDailyByDate = (date: string) => {
|
||||
if (date) {
|
||||
dailyItemInfo.date = formatChineseDate(date, 'MM月DD日 dddd')
|
||||
dailyItemInfo.itemList = []
|
||||
|
||||
foodStore.foodRecordList.forEach((item) => {
|
||||
if (item.date === date) {
|
||||
dailyItemInfo.itemList.push(item)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
dailyItemInfo.itemList = []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击美食记录按钮
|
||||
* @param foodRecord 美食记录
|
||||
*/
|
||||
const selectFoodRecordClick = async (foodRecord: IFoodRecord) => {
|
||||
foodStore.foodRecordApiType = 'UPDATE'
|
||||
|
||||
appStore.isShowMobileBack = true
|
||||
foodStore.currentFoodRecord = foodRecord
|
||||
await router.push({ name: 'MobileHomeFoodRecordForm' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.food-daily-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1vh;
|
||||
padding: 1vh 0.5vw;
|
||||
|
||||
.daily-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
97
src/components/Food/FoodRecipe.vue
Normal file
97
src/components/Food/FoodRecipe.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<div ref="foodRecipeRef" class="food-recipe">
|
||||
<div class="query-item card-item">
|
||||
<span>分类:</span>
|
||||
<el-segmented v-model="foodStore.queryRecipe.category"
|
||||
:options="foodCategoryList"
|
||||
@change="queryFoodSegmented"/>
|
||||
</div>
|
||||
|
||||
<div class="food-list">
|
||||
<el-empty v-if="foodStore.foodRecipeList.length === 0" description="暂无美食记录"/>
|
||||
|
||||
<div v-else class="list">
|
||||
<food-card v-for="(item, index) in foodStore.foodRecipeList"
|
||||
:key="index" :food-item="item"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<arrive-bottom v-if="foodStore.isScrollEnd"/>
|
||||
|
||||
<draggable-button @click="addClick"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import FoodCard from '@/components/Food/FoodCard.vue'
|
||||
import ArriveBottom from '@/components/ArriveButton.vue'
|
||||
import DraggableButton from '@/components/DraggableButton.vue'
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const foodRecipeRef = ref()
|
||||
const foodStore = useFoodStore()
|
||||
const router = useRouter()
|
||||
|
||||
const foodCategoryList = [{
|
||||
label: '全部',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
label: '家常菜',
|
||||
value: '家常菜'
|
||||
}]
|
||||
|
||||
onMounted(async () => {
|
||||
foodRecipeRef.value.addEventListener('scroll', handleScroll)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
foodRecipeRef.value.removeEventListener('scroll', handleScroll)
|
||||
})
|
||||
|
||||
const addClick = () => {
|
||||
foodStore.foodRecordApiType = 'ADD'
|
||||
foodStore.currentFoodRecord = foodStore.getEmptyFoodRecord()
|
||||
router.push({ name: 'MobileHomeFoodRecordForm' })
|
||||
}
|
||||
|
||||
const queryFoodSegmented = async () => {
|
||||
foodStore.pageInfo.currentPage = 1
|
||||
foodStore.foodRecipeList = []
|
||||
await foodStore.queryFoodRecipeList()
|
||||
}
|
||||
|
||||
const handleScroll = () => {
|
||||
const el = foodRecipeRef.value
|
||||
if (el.scrollTop + el.clientHeight >= el.scrollHeight - 10) {
|
||||
const { currentPage, pageSize, totalSize } = foodStore.pageInfo
|
||||
foodStore.isScrollEnd = pageSize * currentPage > totalSize
|
||||
if (!foodStore.isScrollEnd) {
|
||||
foodStore.pageInfo.currentPage++
|
||||
foodStore.queryFoodRecipeList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.food-recipe {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.query-item {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.food-list {
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
75
src/components/Food/FoodTimeline.vue
Normal file
75
src/components/Food/FoodTimeline.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="food-timeline">
|
||||
<el-date-picker
|
||||
v-model="currentYear"
|
||||
type="year" placeholder="请选择年份"
|
||||
style="width: 100px;"
|
||||
@change="changeDateEvent"
|
||||
/>
|
||||
|
||||
<el-timeline v-if="foodStore.foodRecordList.length > 0">
|
||||
<el-timeline-item v-for="(item, index) in foodStore.foodRecordList"
|
||||
:key="index" :timestamp="formatDate(item.date)" placement="top">
|
||||
<div class="timeline-info card-item">
|
||||
<span>{{ item.name }}</span>
|
||||
<el-image :src="item.imageUrl"
|
||||
fit="contain" alt="暂无成果">
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<span>暂无图片</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
|
||||
<el-empty v-else description="暂无日记"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useFoodStore } from '@/store/food.ts'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { formatDate, getCurrentYear, getStartEndDate } from 'vue3-common/utils/dateUtil'
|
||||
import { serviceFileRootPath } from '@/utils'
|
||||
|
||||
const currentYear = ref(new Date())
|
||||
const foodStore = useFoodStore()
|
||||
|
||||
onMounted(async () => {
|
||||
const dateInfo = getStartEndDate(getCurrentYear(), 'year')
|
||||
foodStore.queryDateList[0] = dateInfo[0]
|
||||
foodStore.queryDateList[1] = dateInfo[1]
|
||||
await foodStore.refreshInfo()
|
||||
})
|
||||
|
||||
const changeDateEvent = async (value: Date) => {
|
||||
const dateInfo = getStartEndDate(value, 'year')
|
||||
foodStore.queryDateList[0] = dateInfo[0]
|
||||
foodStore.queryDateList[1] = dateInfo[1]
|
||||
await foodStore.refreshInfo()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.food-timeline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.el-date-editor {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.el-timeline {
|
||||
padding: 10px;
|
||||
|
||||
.timeline-info {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user