feat:重构模块

This commit is contained in:
2025-10-08 22:06:39 +08:00
parent f60d78fe8b
commit 2e81e57774
222 changed files with 2267 additions and 3059 deletions

View File

@@ -0,0 +1,21 @@
<template>
<div class="info-item card-item">
<div class="title-info">
<svg-icon name="home-basic"/>
<span class="sub-title">基础信息</span>
</div>
<div class="info food-basic-info">
<el-tag v-if="foodStore.currentFoodRecipe.category">
{{ foodStore.currentFoodRecipe.category }}
</el-tag>
</div>
</div>
</template>
<script setup lang="ts">
import { SvgIcon } from 'vue3-common'
import { useFoodStore } from '@/store/food.ts'
const foodStore = useFoodStore()
</script>

View File

@@ -0,0 +1,59 @@
<template>
<div class="info-item card-item">
<div class="title-info">
<svg-icon name="home-material"/>
<span class="sub-title">食材信息</span>
</div>
<div class="info material-info">
<div class="material-item">
<span class="material-title">主料</span>
<div v-if="getFoodMaterial('主料').length > 0" class="material-content">
<el-tag v-for="(item, index) in getFoodMaterial('主料')"
:key="index">{{ `${item.name} ${item.amount}` }}
</el-tag>
</div>
<div v-else class="material-content">
<el-tag></el-tag>
</div>
</div>
<div class="material-item">
<span class="material-title">配料</span>
<div v-if="getFoodMaterial('辅料').length > 0" class="material-content">
<el-tag v-for="(item, index) in getFoodMaterial('辅料')"
:key="index">{{ `${item.name} ${item.amount}` }}
</el-tag>
</div>
<div v-else class="material-content">
<el-tag></el-tag>
</div>
</div>
<div class="material-item">
<span class="material-title">辅料</span>
<div v-if="getFoodMaterial('调料').length > 0" class="material-content">
<el-tag v-for="(item, index) in getFoodMaterial('调料')"
:key="index">{{ `${item.name} ${item.amount}` }}
</el-tag>
</div>
<div v-else class="material-content">
<el-tag></el-tag>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { SvgIcon } from 'vue3-common'
const foodStore = useFoodStore()
const getFoodMaterial = (type: string) => {
return foodStore.currentFoodRecipe.materialList.filter((item) => {
return item.type === type
})
}
</script>

View File

@@ -0,0 +1,25 @@
<template>
<div class="info-item card-item">
<div class="title-info">
<svg-icon name="home-others"/>
<span class="sub-title">其他信息</span>
</div>
<div class="others-info">
<span class="others-title">备注</span>
<span v-if="foodStore.currentFoodRecipe.remark">
{{ foodStore.currentFoodRecipe.remark }}
</span>
<span v-else>
</span>
</div>
</div>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { SvgIcon } from 'vue3-common'
const foodStore = useFoodStore()
</script>

View File

@@ -0,0 +1,32 @@
<template>
<div class="info-item">
<div class="title-info">
<svg-icon name="home-work"/>
<span class="sub-title">成果展示</span>
</div>
<div v-if="foodStore.currentFoodRecipe.recordList.length > 0" class="info record-info">
<el-timeline>
<el-timeline-item v-for="(item, index) in foodStore.currentFoodRecipe.recordList"
:key="index" :timestamp="item.date" placement="top">
<el-card class="record-card">
<span class="record-content">完成人{{ item.person }}</span>
<el-image v-if="item.imageUrl"
:src="serviceFileRootPath + item.imageUrl"
:preview-src-list="[serviceFileRootPath + item.imageUrl]"
fit="contain" alt="暂无图片" class="record-image"/>
</el-card>
</el-timeline-item>
</el-timeline>
</div>
<el-empty v-else description="暂无成果"/>
</div>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { serviceFileRootPath } from '@/utils'
import { SvgIcon } from 'vue3-common'
const foodStore = useFoodStore()
</script>

View File

@@ -0,0 +1,28 @@
<template>
<div class="info-item card-item">
<div class="title-info">
<svg-icon name="home-step"/>
<span class="sub-title">菜谱信息</span>
</div>
<div class="step-info">
<div v-for="(item, index) in foodStore.currentFoodRecipe.stepList"
:key="index" class="step-container">
<span class="step-number">{{ index + 1 }}</span>
<div class="step-body">
<span class="step-content">{{ item.content }}</span>
<img v-if="item.imageUrl"
:src="serviceFileRootPath + item.imageUrl" alt="暂无图片" class="step-image"/>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { serviceFileRootPath } from '@/utils'
import { SvgIcon } from 'vue3-common'
const foodStore = useFoodStore()
</script>

View File

@@ -0,0 +1,102 @@
<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"/>
</div>
</template>
<script setup lang="ts">
import FoodDailyItem from '@/components/Food/FoodDailyItem.vue'
import { reactive, watch, ref, onMounted } from 'vue'
import { useFoodStore } from '@/store/food.ts'
import { formatDate } from 'vue3-common/utils/dateUtil'
import { CalendarDay } from 'v-calendar/dist/types/src/utils/page'
import { useAppStore } from '@/store/app.ts'
const calendarRef = ref()
const foodStore = useFoodStore()
const appStore = useAppStore()
onMounted(() => {
const { month } = foodStore.queryRecord
calendarRef.value.move(new Date(month))
})
watch(() => foodStore.isRefresh, () => {
updateInfo()
})
const dailyInfo = reactive({
calendarDate: '',
attrs: [] as any[]
})
const updateInfo = () => {
getCalendarMoveDate()
getCalendarAttrDates()
}
const getCalendarMoveDate = () => {
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: CalendarDay) => {
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,130 @@
<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 v-if="!isMobile()"
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.ts'
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: 'FoodDetailId', 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/home/life.record.module";
</style>

View File

@@ -0,0 +1,117 @@
<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'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { isMobile } from 'vue3-common/utils/layoutUtil'
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'
if (isMobile()) {
appStore.isShowMobileBack = true
foodStore.currentFoodRecord = foodRecord
await router.push({ name: 'FoodRecordForm' })
} else {
foodStore.foodRecordDialog = {
title: '编辑记录',
visible: true,
data: deepCopyObject(foodRecord)
}
}
}
</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,57 @@
<template>
<div class="food-daily-list">
<el-date-picker v-model="foodStore.queryRecord.year"
type="year" :editable="false" style="width: 120px;"
@change="foodStore.refreshInfo()"/>
<el-empty v-if="foodDailyInfo.dateList.length === 0"/>
<el-scrollbar>
<food-daily-item v-for="(item, index) in foodDailyInfo.dateList"
:key="index" :date="item"/>
</el-scrollbar>
</div>
</template>
<script setup lang="ts">
import FoodDailyItem from '@/components/Food/FoodDailyItem.vue'
import { reactive, watch } from 'vue'
import { useFoodStore } from '@/store/food.ts'
const foodStore = useFoodStore()
watch(() => foodStore.isRefresh, () => {
updateInfo()
})
const foodDailyInfo = reactive({
dateList: [] as string[]
})
const updateInfo = () => {
const dateList = foodStore.foodRecordList.map((item) => item.date)
foodDailyInfo.dateList = [...new Set(dateList)] as string[]
}
</script>
<style lang="scss">
.food-daily-list {
height: calc(100vh - 120px);
overflow: auto;
display: flex;
flex-direction: column;
gap: 1vh;
.el-date-editor {
align-self: center;
}
.el-scrollbar {
.el-scrollbar__view {
display: flex;
flex-direction: column;
gap: 2vh;
}
}
}
</style>

View File

@@ -0,0 +1,116 @@
<template>
<el-dialog v-model="dialogData.visible" :title="dialogData.title"
center @open="openDialogEvent" class="food-dialog">
<el-form ref="formRef" :model="dialogData.data"
:rules=rules label-width="80px">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="美食名称" prop="name">
<el-input v-model="dialogData.data.name" autocomplete="off"
placeholder="请输入美食名称" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
<el-form-item label="美食菜系" prop="category">
<el-select v-model="dialogData.data.category">
<el-option v-for="(item, index) in foodCategoryList"
:key="index" :label="item" :value="item"/>
</el-select>
</el-form-item>
<el-form-item label="推荐指数" prop="recommendRate">
<el-rate v-model="dialogData.data.recommendRate"
:colors="foodStore.rateColorList"/>
</el-form-item>
<el-form-item label="食材">
<food-material-form />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="步骤">
<food-step-form />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="dialogData.data.remark" autocomplete="off"
placeholder="请输入备注" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">保存</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import FoodMaterialForm from '@/components/Food/Form/FoodMaterialForm.vue'
import FoodStepForm from '@/components/Food/Form/FoodStepForm.vue'
import { useFoodStore } from '@/store/food.ts'
import { FormInstance, FormRules } from 'element-plus'
import { computed, nextTick, ref, reactive } from 'vue'
import { foodRules } from '@/utils/element/elRules.ts'
const rules = reactive<FormRules>(foodRules)
const formRef = ref<FormInstance>()
const foodStore = useFoodStore()
const foodCategoryList = ['家常菜', '淮扬菜', '鲁菜', '川菜', '粤菜', '闽菜', '浙菜', '湘菜', '徽菜', '其他']
const dialogData = computed(() => foodStore.foodRecipeDialog)
/**
* 打开对话框事件
*/
const openDialogEvent = async () => {
await nextTick(() => {
formRef.value?.clearValidate()
})
}
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value?.validate().then(() => {
foodStore.handleFoodRecipeApi()
})
}
const cancelClick = () => {
dialogData.value.visible = false
}
</script>
<style lang="scss">
.food-dialog {
width: 75%;
.el-dialog__body {
height: 65vh;
.el-button.is-text {
padding: 0;
}
.material-table {
height: 45vh;
}
.food-step-table {
height: 55vh;
}
}
}
</style>

View File

@@ -0,0 +1,68 @@
<template>
<div v-if="isMobile()" class="mobile-common-query card-item">
<div class="query-item">
<span>分类</span>
<el-segmented v-model="foodStore.queryRecipe.category"
:options="queryCategoryList"
@change="queryFoodSegmented"/>
</div>
</div>
<div v-else class="common-query">
<el-select v-model="foodStore.queryDateType"
@change="foodStore.refreshInfo()"
style="width: 120px;align-self: center;">
<el-option v-for="(item, index) in dateTypeOptions" :key="index"
:label="item.label" :value="item.value"/>
</el-select>
</div>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { onMounted, ref } from 'vue'
import { ICategory } from '@/types'
const foodStore = useFoodStore()
const queryCategoryList = ref([] as ICategory[])
onMounted(async () => {
await foodStore.queryFoodCategory()
initCategoryList()
})
const dateTypeOptions = [{
label: '按月选择',
value: 'month'
},
{
label: '按年选择',
value: 'year'
}]
const initCategoryList = () => {
queryCategoryList.value = []
queryCategoryList.value.push({
label: '全部',
value: ''
})
foodStore.foodCategoryList.forEach((item) => {
queryCategoryList.value.push({
label: item,
value: item
})
})
}
const queryFoodSegmented = async () => {
foodStore.pageInfo = {
currentPage: 1,
pageSize: 3,
totalSize: 1
}
foodStore.foodRecipeList = []
await foodStore.queryMobileFoodRecipeList()
}
</script>

View File

@@ -0,0 +1,45 @@
<template>
<div class="food-list card-item">
<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>
<el-pagination
v-model:current-page="foodStore.pageInfo.currentPage"
:page-size="foodStore.pageInfo.pageSize"
layout="total, prev, pager, next"
:total="foodStore.pageInfo.totalSize"
@change="changePageEvent"
/>
<el-button v-if="isLargeScreen()"
type="primary" :icon="Plus" circle
@click="addFoodRecipeClick" class="add-new" />
</div>
</template>
<script setup lang="ts">
import { Plus } from '@element-plus/icons-vue'
import FoodCard from '@/components/Food/FoodCard.vue'
import { useFoodStore } from '@/store/food.ts'
import { isLargeScreen } from '@/utils'
const foodStore = useFoodStore()
const addFoodRecipeClick = () => {
foodStore.foodRecipeApiType = 'ADD'
foodStore.foodRecipeDialog = {
title: '新增菜谱',
visible: true,
data: foodStore.getEmptyFoodRecipe()
}
}
const changePageEvent = (value: number) => {
foodStore.pageInfo.currentPage = value
foodStore.queryFoodRecipeList()
}
</script>

View File

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

View File

@@ -0,0 +1,92 @@
<template>
<el-dialog v-model="foodStore.foodMaterialDialog.visible"
:title="foodStore.foodMaterialDialog.title"
center @open="openDialogEvent"
class="food-material-dialog">
<el-form ref="formRef" :model="foodStore.foodMaterialDialog.data"
:rules="rules">
<el-form-item label="类型" prop="type">
<el-select
v-model="foodStore.foodMaterialDialog.data.type"
placeholder="请选择类型">
<el-option
v-for="(item, index) in foodMaterialTypeList"
:key="index" :label="item" :value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="foodStore.foodMaterialDialog.data.name" autocomplete="off"
placeholder="请输入名称" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
<el-form-item label="分量" prop="amount">
<el-input v-model="foodStore.foodMaterialDialog.data.amount" autocomplete="off"
placeholder="请输入分量1斤/2勺/适量)" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">保存</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { useFoodStore } from '@/store/food.ts'
import { nextTick, reactive, ref } from 'vue'
import { FormRules } from 'element-plus'
import { foodMaterialRules } from '@/utils/element/elRules.ts'
const rules = reactive<FormRules>(foodMaterialRules)
const formRef = ref()
const foodStore = useFoodStore()
const foodMaterialTypeList = ['主料', '辅料', '调料']
/**
* 打开对话框事件
*/
const openDialogEvent = () => {
nextTick(() => {
formRef.value?.clearValidate()
})
}
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value.validate().then(async () => {
if (foodStore.foodMaterialApiType === 'UPDATE') {
const { editFoodMaterialIndex: index } = foodStore
foodStore.foodRecipeDialog.data.materialList[index] = foodStore.foodMaterialDialog.data
} else {
foodStore.foodRecipeDialog.data.materialList.push(foodStore.foodMaterialDialog.data)
}
foodStore.foodMaterialDialog.visible = false
})
}
const cancelClick = () => {
foodStore.foodMaterialDialog.visible = false
}
</script>
<style lang="scss">
.food-material-dialog {
width: 30%;
.el-dialog__body {
// height: 50vh;
}
}
</style>

View File

@@ -0,0 +1,56 @@
<template>
<el-table :data="foodStore.foodRecipeDialog.data.materialList"
border stripe class="material-table">
<el-table-column type="index" align="center" width="90">
<template #header>
<el-button type="primary" @click="handleAddClick">新增</el-button>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" align="center" min-width="10%"/>
<el-table-column prop="name" label="名称" align="center" min-width="20%"/>
<el-table-column prop="amount" label="分量" align="center" min-width="20%"/>
<el-table-column label="操作" align="center" width="100">
<template #default="scope">
<el-button type="primary" text
@click="handleEditClick(scope.$index, scope.row)">编辑</el-button>
<el-button type="danger" text
@click="handleDeleteClick(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { IFoodMaterial } from '@/types/food.ts'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
const foodStore = useFoodStore()
const handleAddClick = () => {
foodStore.foodMaterialApiType = 'ADD'
foodStore.foodMaterialDialog = {
title: '新增食材',
visible: true,
data: foodStore.getEmptyFoodMaterial()
}
}
const handleEditClick = (index: number, material: IFoodMaterial) => {
foodStore.editFoodMaterialIndex = index
foodStore.foodMaterialApiType = 'UPDATE'
foodStore.foodMaterialDialog = {
title: '编辑食材',
visible: true,
data: deepCopyObject(material)
}
}
const handleDeleteClick = (index: number, material: IFoodMaterial) => {
foodStore.foodMaterialApiType = 'DELETE'
commonElMessageBox(`确认删除该食材: ${material.name}?`).then(() => {
foodStore.foodRecipeDialog.data.materialList.splice(index, 1)
})
}
</script>

View File

@@ -0,0 +1,107 @@
<template>
<el-dialog v-model="dialogData.visible" :title="dialogData.title"
center @open="openDialogEvent" class="food-record-dialog">
<el-form ref="formRef" :model="dialogData.data"
:rules="rules" label-width="80">
<el-form-item label="菜谱名称" prop="name">
<el-select
v-model="dialogData.data.name"
@focus="foodStore.queryFoodNameList()"
:disabled="foodStore.foodRecordApiType === 'UPDATE'"
placeholder="请选择菜谱名称">
<el-option
v-for="(item, index) in foodStore.foodNameList"
:key="index" :label="item" :value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="完成时间" prop="date">
<el-date-picker
v-model="dialogData.data.date"
type="date" :editable="false" placeholder="请选择完成时间"
value-format="YYYY-MM-DD"/>
</el-form-item>
<el-form-item label="完成人" prop="person">
<el-select
v-model="dialogData.data.person"
placeholder="请选择完成人">
<el-option
v-for="(item, index) in ['哥哥', '宝宝']"
:key="index" :label="item" :value="item"
/>
</el-select>
</el-form-item>
<el-form-item label="图片">
<upload-image v-model="dialogData.data.imageUrl"
:service-url="fileServiceUrl"
:image-limit="1"
:service-file-root-path="serviceFileRootPath"
:image-path="foodStore.foodImagePath"/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">保存</el-button>
<el-button v-if="foodStore.foodRecordApiType === 'UPDATE'"
type="danger" @click="deleteClick">删除</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { UploadImage } from 'vue3-common'
import { useFoodStore } from '@/store/food.ts'
import { computed, ref, reactive, nextTick } from 'vue'
import { FormInstance, FormRules } from 'element-plus'
import { foodRecordRules } from '@/utils/element/elRules.ts'
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
const rules = reactive<FormRules>(foodRecordRules)
const formRef = ref<FormInstance>()
const foodStore = useFoodStore()
const dialogData = computed(() => foodStore.foodRecordDialog)
/**
* 打开对话框事件
*/
const openDialogEvent = async () => {
await nextTick(() => {
formRef.value?.clearValidate()
})
}
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value?.validate().then(() => {
foodStore.handleFoodRecordApi(foodStore.foodRecordDialog.data)
})
}
const cancelClick = () => {
dialogData.value.visible = false
}
const deleteClick = () => {
commonElMessageBox('是否确认删除该记录?').then(() => {
foodStore.foodRecordApiType = 'DELETE'
foodStore.handleFoodRecordApi(dialogData.value.data)
})
}
</script>
<style lang="scss">
.food-record-dialog {
max-width: 600px;
}
</style>

View File

@@ -0,0 +1,84 @@
<template>
<el-dialog v-model="foodStore.foodStepDialog.visible"
:title="foodStore.foodStepDialog.title"
center @open="openDialogEvent"
class="food-step-dialog">
<el-form ref="formRef" :model="foodStore.foodStepDialog.data"
:rules="rules">
<el-form-item label="内容" prop="content">
<el-input v-model="foodStore.foodStepDialog.data.content" autocomplete="off"
placeholder="请输入内容" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
<el-form-item label="图片">
<upload-image v-model="foodStore.foodStepDialog.data.imageUrl"
:service-url="fileServiceUrl"
:image-limit="1"
:service-file-root-path="serviceFileRootPath"
:image-path="foodStore.foodImagePath"/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancelClick">取消</el-button>
<el-button type="primary" @click="confirmClick">保存</el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { UploadImage } from 'vue3-common'
import { useFoodStore } from '@/store/food.ts'
import { ref, reactive, nextTick } from 'vue'
import { FormRules } from 'element-plus'
import { foodStepRules } from '@/utils/element/elRules.ts'
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
const rules = reactive<FormRules>(foodStepRules)
const formRef = ref()
const foodStore = useFoodStore()
/**
* 打开对话框事件
*/
const openDialogEvent = () => {
nextTick(() => {
formRef.value?.clearValidate()
})
}
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value.validate()
.then(async () => {
if (foodStore.foodStepApiType === 'UPDATE') {
const { editFoodStepIndex: index } = foodStore
foodStore.foodRecipeDialog.data.stepList[index] = foodStore.foodStepDialog.data
} else {
foodStore.foodRecipeDialog.data.stepList.push(foodStore.foodStepDialog.data)
}
foodStore.foodStepDialog.visible = false
})
}
const cancelClick = () => {
foodStore.foodStepDialog.visible = false
}
</script>
<style lang="scss">
.food-step-dialog {
width: 30%;
.el-dialog__body {
// height: 50vh;
}
}
</style>

View File

@@ -0,0 +1,79 @@
<template>
<el-table :data="foodStore.foodRecipeDialog.data.stepList"
border class="food-step-table">
<el-table-column type="index" align="center" width="90">
<template #header>
<el-button type="primary" @click="handleAddClick">新增</el-button>
</template>
</el-table-column>
<el-table-column prop="content" label="内容" align="center" min-width="20%" />
<el-table-column prop="imageUrl" label="图片" align="center" min-width="20%">
<template #default="scope">
<el-image v-if="scope.row.imageUrl" :src="serviceFileRootPath + scope.row.imageUrl" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="100">
<template #default="scope">
<el-button type="primary" text
@click="handleEditClick(scope.$index, scope.row)">编辑</el-button>
<el-button type="danger" text
@click="handleDeleteClick(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food.ts'
import { IFoodStep } from '@/types/food.ts'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { serviceFileRootPath } from '@/utils'
const foodStore = useFoodStore()
const handleAddClick = () => {
foodStore.foodStepApiType = 'ADD'
foodStore.foodStepImageList = []
foodStore.foodStepDialog = {
title: '新增步骤',
visible: true,
data: foodStore.getEmptyFoodStep()
}
}
const handleEditClick = (index: number, step: IFoodStep) => {
foodStore.editFoodStepIndex = index
foodStore.foodStepApiType = 'UPDATE'
foodStore.foodStepDialog = {
title: '编辑步骤',
visible: true,
data: deepCopyObject(step)
}
}
const handleDeleteClick = (index: number) => {
commonElMessageBox('确认删除该步骤?').then(() => {
foodStore.foodStepApiType = 'DELETE'
foodStore.foodRecipeDialog.data.stepList.splice(index, 1)
foodStore.foodRecipeDialog.data.stepList.forEach((item) => {
if (item.sort > index) {
item.sort -= 1
}
})
})
}
</script>
<style lang="scss">
.food-step-table {
.el-image {
width: 100%;
height: 100%;
max-height: 200px;
max-width: 200px;
}
}
</style>