feat:搭建基本框架

This commit is contained in:
2025-06-23 19:39:45 +08:00
parent de5537ee1b
commit e09aed87d1
38 changed files with 2106 additions and 83 deletions

View 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>

View 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>

View 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>

View 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>

View 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>