feat:增加美食表单功能

This commit is contained in:
2025-06-24 20:00:06 +08:00
parent 4956c1822e
commit 69c0bfa4c7
18 changed files with 836 additions and 92 deletions

81
src/apis/food.ts Normal file
View File

@@ -0,0 +1,81 @@
import { cloudService } from './index'
import type { IFoodQuery, IFoodRecipe, IFoodRecord, IFoodStats } from '@/types/food'
export const addFoodRecipeApi = (foodRecipe: IFoodRecipe): Promise<boolean> =>
cloudService({
url: '/food-service/food/recipe',
method: 'post',
data: foodRecipe
})
export const updateFoodRecipeApi = (id: number, foodRecipe: IFoodRecipe): Promise<boolean> =>
cloudService({
url: `/food-service/food/recipe/${id}`,
method: 'put',
data: foodRecipe
})
export const deleteFoodRecipeApi = (id: number): Promise<boolean> =>
cloudService({
url: `/food-service/food/recipe/${id}`,
method: 'DELETE'
})
export const queryFoodRecipeByIdApi = (id: number): Promise<any> =>
cloudService({
url: `/food-service/food/recipe/${id}`,
method: 'get'
})
export const queryFoodRecipeApi = (currentPage: number, pageSize: number,
query: IFoodQuery): Promise<any> =>
cloudService({
url: '/food-service/food/recipe/page',
method: 'get',
params: { currentPage, pageSize, ...query }
})
export const queryFoodNameListApi = (): Promise<string[]> =>
cloudService({
url: '/food-service/food/recipe/name',
method: 'get'
})
export const addFoodRecordApi = (foodRecord: IFoodRecord): Promise<boolean> =>
cloudService({
url: '/food-service/food/record',
method: 'post',
data: foodRecord
})
export const updateFoodRecordApi = (id: number, foodRecord: IFoodRecord): Promise<boolean> =>
cloudService({
url: `/food-service/food/record/${id}`,
method: 'put',
data: foodRecord
})
export const deleteFoodRecordApi = (id: number): Promise<boolean> =>
cloudService({
url: `/food-service/food/record/${id}`,
method: 'DELETE'
})
export const queryFoodRecordApi = (startDate: string, endDate: string): Promise<IFoodRecord[]> =>
cloudService({
url: '/food-service/food/record',
method: 'get',
params: { startDate, endDate }
})
export const queryFoodCategoryApi = (): Promise<string[]> =>
cloudService({
url: '/food-service/food/category',
method: 'get'
})
export const queryFoodStatsApi = (): Promise<IFoodStats> =>
cloudService({
url: '/food-service/food/stats',
method: 'get'
})

3
src/apis/index.ts Normal file
View File

@@ -0,0 +1,3 @@
import createService from 'vue3-common/utils/axiosUtil'
export const cloudService = createService()

9
src/apis/session.ts Normal file
View File

@@ -0,0 +1,9 @@
import { cloudService } from './index'
import type { ISession } from '@/types/auth'
export const loginApi = (name: string, password: string): Promise<ISession> =>
cloudService({
url: '/food-service/session',
method: 'post',
params: { name, password }
})

View File

@@ -6,24 +6,19 @@
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
@@ -39,12 +34,6 @@ const dailyInfo = reactive({
attrs: [] as any[]
})
const addClick = () => {
foodStore.foodRecordApiType = 'ADD'
foodStore.currentFoodRecord = foodStore.getEmptyFoodRecord()
router.push({ name: 'MobileHomeFoodRecordForm' })
}
const updateInfo = () => {
getCalendarMoveDate()
getCalendarAttrDates()

View File

@@ -0,0 +1,58 @@
<template>
<div class="food-material-form">
<div class="button-container">
<el-button type="primary" @click="addClick">新增</el-button>
</div>
<el-table :data="foodStore.currentFoodRecipe.materialList"
border stripe class="material-table">
<el-table-column type="index" align="center" width="40" />
<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>
</div>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food'
import { IFoodMaterial } from '@/types/food'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
const foodStore = useFoodStore()
const addClick = () => {
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.currentFoodRecipe.materialList.splice(index, 1)
})
}
</script>
<style lang="scss">
.food-material-form {
width: 100%;
display: flex;
flex-direction: column;
gap: 5px;
}
</style>

View File

@@ -17,22 +17,17 @@
</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: '全部',
@@ -51,12 +46,6 @@ 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 = []

View File

@@ -0,0 +1,80 @@
<template>
<div class="food-step-form">
<div class="button-container">
<el-button type="primary" @click="addClick">新增</el-button>
</div>
<el-table :data="foodStore.currentFoodRecipe.stepList"
border class="food-step-table">
<el-table-column type="index" align="center" width="40" />
<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>
</div>
</template>
<script setup lang="ts">
import { useFoodStore } from '@/store/food'
import { IFoodStep } from '@/types/food'
import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { serviceFileRootPath } from '@/utils'
const foodStore = useFoodStore()
const addClick = () => {
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.currentFoodRecipe.stepList.splice(index, 1)
foodStore.currentFoodRecipe.stepList.forEach((item) => {
if (item.sort > index) {
item.sort -= 1
}
})
})
}
</script>
<style lang="scss">
.food-step-form {
width: 100%;
display: flex;
flex-direction: column;
gap: 5px;
.food-step-table {
.el-image {
width: 100%;
height: 100%;
max-height: 200px;
max-width: 200px;
}
}
}
</style>

127
src/store/auth.ts Normal file
View File

@@ -0,0 +1,127 @@
import { defineStore } from 'pinia'
import type { ILoginType, ISession } from '@/types/auth'
import { loginApi } from '@/apis/session'
import { ElMessage } from 'element-plus'
import { emailPattern, phonePattern } from '@/utils/element/elRules'
export const useAuthStore = defineStore('auth', {
state: () => ({
loginForm: {
username: '132',
password: '123',
isRemember: false
},
phoneLoginForm: {
phoneNumber: '',
code: ''
},
emailLoginForm: {
email: '',
code: ''
},
loginType: 'common' as ILoginType,
loginLoading: false,
loginAdminLoading: false,
isVerified: false,
isGettingCode: false,
getCodeCountdown: 60,
getCodeTimer: null as NodeJS.Timeout | null,
homeSession: {} as ISession
}),
actions: {
changeLoginType(type: ILoginType) {
this.loginType = type
if (type === 'common') {
this.stopCountdown()
}
this.resetLoginForm()
},
getAuthCode(type: ILoginType) {
switch (type) {
case 'phone':
if (phonePattern.test(this.phoneLoginForm.phoneNumber)) {
this.isGettingCode = true
ElMessage.success('验证码已发送至手机')
this.startCountdown()
} else {
ElMessage.error('请先输入正确的手机号')
}
break
case 'email':
if (emailPattern.test(this.emailLoginForm.email)) {
this.isGettingCode = true
ElMessage.success('验证码已发送至邮箱')
this.startCountdown()
} else {
ElMessage.error('请先输入正确的邮箱地址')
}
break
default:
break
}
},
startCountdown() {
this.getCodeTimer = setInterval(() => {
if (this.getCodeCountdown > 0) {
this.getCodeCountdown--
} else {
clearInterval(this.getCodeTimer as NodeJS.Timeout)
this.isGettingCode = false
this.getCodeCountdown = 60
}
}, 1000)
},
stopCountdown() {
if (this.getCodeTimer) {
clearInterval(this.getCodeTimer)
}
},
async handleLogin(type: ILoginType) {
const { username, password } = this.loginForm
const { phoneNumber, code: phoneCode } = this.phoneLoginForm
const { email, code: emailCode } = this.emailLoginForm
switch (type) {
case 'common':
console.log(username)
console.log(password)
this.homeSession = await loginApi(username, password)
break
case 'phone':
this.homeSession = await loginApi(phoneNumber, phoneCode)
break
case 'email':
this.homeSession = await loginApi(email, emailCode)
break
default:
break
}
ElMessage.success('登录成功')
},
resetLoginForm() {
switch (this.loginType) {
case 'common':
this.loginForm = {
username: '',
password: '',
isRemember: false
}
break
case 'phone':
this.phoneLoginForm = {
phoneNumber: '',
code: ''
}
break
case 'email':
this.emailLoginForm = {
email: '',
code: ''
}
break
default:
break
}
}
}
})

View File

@@ -3,11 +3,11 @@ import type {
IFoodRecipe, IFoodMaterial, IFoodStep,
IFoodStats, IFoodRecord
} from '@/types/food'
// import {
// addFoodRecipeApi, addFoodRecordApi, deleteFoodRecipeApi, deleteFoodRecordApi,
// queryFoodNameListApi, queryFoodRecipeByIdApi, queryFoodRecipeApi, queryFoodRecordApi,
// queryFoodStatsApi, updateFoodRecipeApi, updateFoodRecordApi, queryFoodCategoryApi
// } from '@/apis/food.ts'
import {
addFoodRecipeApi, addFoodRecordApi, deleteFoodRecipeApi, deleteFoodRecordApi,
queryFoodNameListApi, queryFoodRecipeByIdApi, queryFoodRecipeApi, queryFoodRecordApi,
queryFoodStatsApi, updateFoodRecipeApi, updateFoodRecordApi, queryFoodCategoryApi
} from '@/apis/food.ts'
// import { IDateType, ImagePathEnum } from '@/types'
import type { IDialog, IHandleApi } from 'vue3-common/types'
import { ElMessage } from 'element-plus'
@@ -47,11 +47,6 @@ export const useFoodStore = defineStore('food', {
queryDateType: 'month',
queryDateList: [] as string[],
foodRecipeApiType: 'ADD' as IHandleApi,
foodRecipeDialog: {
visible: false,
title: '',
data: {}
} as IDialog<IFoodRecipe>,
foodMaterialApiType: 'ADD' as IHandleApi,
foodMaterialDialog: {
visible: false,
@@ -67,11 +62,6 @@ export const useFoodStore = defineStore('food', {
} as IDialog<IFoodStep>,
editFoodStepIndex: 0,
foodRecordApiType: 'ADD' as IHandleApi,
foodRecordDialog: {
visible: false,
title: '',
data: {}
} as IDialog<IFoodRecord>,
editFoodRecordIndex: 0,
foodImagePath: 'food',
rateColorList: ['#E2DEDC', '#F7BA2A', '#009FA8'],
@@ -92,21 +82,9 @@ export const useFoodStore = defineStore('food', {
actions: {
async queryFoodRecipeList() {
const { currentPage, pageSize } = this.pageInfo
// const result = await queryFoodRecipeApi(currentPage, pageSize, this.queryRecipe)
const foodRecord: IFoodRecipe = {
category: '家常菜',
materialList: [],
name: '红烧鱼',
recommendRate: 4,
recordList: [],
remark: '',
stepList: []
}
this.foodRecipeList = []
this.foodRecipeList.push(foodRecord)
this.foodRecipeList.push(foodRecord)
// this.foodRecipeList.push(...result.records)
// this.pageInfo.totalSize = result.total
const result = await queryFoodRecipeApi(currentPage, pageSize, this.queryRecipe)
this.foodRecipeList.push(...result.records)
this.pageInfo.totalSize = result.total
},
async queryFoodRecordList() {
const { year, month } = this.queryRecord
@@ -147,19 +125,18 @@ export const useFoodStore = defineStore('food', {
async queryFoodCategory() {
// this.foodCategoryList = await queryFoodCategoryApi()
},
async handleFoodRecipeApi(id?: number) {
const { data } = this.foodRecipeDialog
async handleFoodRecipeApi(foodRecipe: IFoodRecipe) {
switch (this.foodRecipeApiType) {
case 'ADD':
// await addFoodRecipeApi(data)
// await addFoodRecipeApi(foodRecipe)
ElMessage.success('新增菜谱成功')
break
case 'UPDATE':
// await updateFoodRecipeApi(data.id as number, data)
// await updateFoodRecipeApi(foodRecipe.id as number, foodRecipe)
ElMessage.success('更新菜谱成功')
break
case 'DELETE':
// await deleteFoodRecipeApi(id as number)
// await deleteFoodRecipeApi(foodRecipe.id as number)
ElMessage.success('删除菜谱成功')
break
default:

75
src/store/user.ts Normal file
View File

@@ -0,0 +1,75 @@
import { defineStore } from 'pinia'
import { getLocalStorage, setLocalStorage, removeLocalStorage } from 'vue3-common/utils/storageUtil'
import type { ILoginForm } from 'vue3-common'
import type { ISession, IUser } from '@/types/auth'
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: {
id: 0,
accountId: 0,
accountName: '',
username: '',
gender: 0,
idNumber: '',
phoneNumber: '',
email: '',
birthDate: '',
avatar: '',
description: '',
area: ['', ''],
address: '',
job: '',
tags: []
} as IUser
}),
actions: {
setLoginForm(loginForm: ILoginForm) {
setLocalStorage('loginForm', JSON.stringify(loginForm))
},
getLoginForm() {
return getLocalStorage('loginForm')
},
removeLoginForm() {
removeLocalStorage('loginForm')
},
handleSession(session: ISession) {
this.setToken(session.saToken.tokenValue)
// this.setUserId(session.userInfo.id)
// this.userInfo = session.userInfo
},
async queryUser() {
// this.userInfo = await queryUserApi(Number(this.getUserId()))
},
async updateUser() {
// await updateUserApi(this.userInfo.id, this.userInfo)
},
async updateAccountPassword(oldPassword: string, newPassword: string) {
console.log(this.userInfo.accountId)
// await updateAccountPasswordApi(this.userInfo.accountId, oldPassword, newPassword)
},
setToken(token: string) {
setLocalStorage('token', token)
},
getToken() {
return getLocalStorage('token')
},
setUserId(userId: number) {
setLocalStorage('userId', String(userId))
},
getUserId() {
return getLocalStorage('userId')
},
setAccount(accountName: string) {
setLocalStorage('account', accountName)
},
getAccount() {
return getLocalStorage('account')
},
removeAll() {
removeLocalStorage('token')
removeLocalStorage('account')
}
}
})

48
src/types/auth.ts Normal file
View File

@@ -0,0 +1,48 @@
export type ILoginType = 'common' | 'phone' | 'email' | 'admin'
export type IAccountType = 'QQ' | 'WeChat' | 'Phone' | 'EMail'
export interface IUser {
id: number;
accountId: number;
accountName: string;
username: string;
gender: number;
idNumber: string;
phoneNumber: string;
email: string;
birthDate: string;
avatar: string;
description: string;
area: string[];
address: string;
job: string;
tags: string[];
}
export interface ISession {
saToken: {
tokenName: string;
tokenValue: string;
isLogin: boolean;
loginId: string;
loginType: string;
tokenTimeout: number;
sessionTimeout: number;
tokenSessionTimeout: number;
tokenActiveTimeout: number;
loginDevice: string;
tag: string;
};
userInfo: IUser;
}
export interface IAccount {
id: number;
accountName: string;
password: string;
confirmPassword: string;
username: string;
state: number;
date: string;
}

View File

@@ -1,5 +1,35 @@
export type IFoodSegment = 'recipe' | 'daily' | 'timeline'
/**
* 食材信息
*/
export interface IFoodMaterial {
type: string;
name: string;
amount: string;
}
/**
* 步骤信息
*/
export interface IFoodStep {
sort: number;
content: string;
imageUrl: string;
}
/**
* 成果信息
*/
export interface IFoodRecord {
id: number;
name: string;
category: string;
date: string;
person: string;
imageUrl: string;
}
/**
* 菜谱信息
*/
@@ -35,36 +65,6 @@ export interface IFoodRecipe {
recordList: IFoodRecord[];
}
/**
* 食材信息
*/
export interface IFoodMaterial {
type: string;
name: string;
amount: string;
}
/**
* 步骤信息
*/
export interface IFoodStep {
sort: number;
content: string;
imageUrl: string;
}
/**
* 成果信息
*/
export interface IFoodRecord {
id: number;
name: string;
category: string;
date: string;
person: string;
imageUrl: string;
}
/**
* 统计信息
*/

View File

@@ -0,0 +1,71 @@
export const chinesePattern = /^[\u4e00-\u9fa5a-zA-Z0-9]+$/
export const amountPattern = /^\d+(\.?\d{1,2})?$/
export const phonePattern = /^1[3-9]\d{9}$/
export const emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
export const loginRules = {
username: [
{ required: true, message: '请输入账号', trigger: 'blur' },
{ pattern: /^[0-9a-zA-Z]+$/, message: '请输入正确的格式: 数字或英文字母', trigger: ['blur'] }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ pattern: /^[0-9a-zA-Z]+$/, message: '请输入正确的格式: 数字或英文字母', trigger: ['blur'] }
],
phoneNumber: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: phonePattern, message: '请输入正确的手机号格式', trigger: ['blur'] }
],
email: [
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
{ pattern: emailPattern, message: '请输入正确的邮箱格式', trigger: ['blur'] }
],
code: [
{ required: true, message: '请输入验证码', trigger: 'blur' },
{ pattern: /^\d{6}$/, message: '请输入6位数字验证码', trigger: ['blur'] }
]
}
export const foodRules = {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' },
{ pattern: new RegExp(chinesePattern), message: '请输入中英文或数字' }
],
category: [
{ required: true, message: '请选择菜系', trigger: 'change' }
]
}
export const foodMaterialRules = {
type: [
{ required: true, message: '请选择类型', trigger: 'change' }
],
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
amount: [
{ required: true, message: '请输入分量', trigger: 'blur' }
]
}
export const foodStepRules = {
content: [
{ required: true, message: '请输入内容', trigger: 'blur' }
]
}
export const foodRecordRules = {
name: [
{ required: true, message: '请选择菜谱名称', trigger: 'change' }
],
date: [
{ required: true, message: '请选择完成时间', trigger: 'change' }
],
person: [
{ required: true, message: '请选择完成人', trigger: 'change' }
]
}

View File

@@ -5,19 +5,23 @@
</template>
<script setup lang="ts">
import { useAuthStore } from '@/store/auth'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/store/user'
import { isMobile } from 'vue3-common/utils/layoutUtil'
import { ElMessage } from 'element-plus'
const authStore = useAuthStore()
const router = useRouter()
const userStore = useUserStore()
const loginClick = () => {
const loginClick = async () => {
if (!isMobile()) {
ElMessage.error('暂不支持PC端')
return
}
router.push('/record')
await authStore.handleLogin('common')
userStore.handleSession(authStore.homeSession)
await router.push('/record')
}
</script>

View File

@@ -28,6 +28,8 @@
<div v-if="foodInfo.recordSegment === 'timeline'" class="food-calendar">
<food-timeline />
</div>
<draggable-button v-show="foodInfo.recordSegment !== 'timeline'" @click="addClick"/>
</div>
</template>
@@ -35,12 +37,17 @@
import FoodRecipe from '@/components/Food/FoodRecipe.vue'
import FoodDaily from '@/components/Food/FoodDaily.vue'
import FoodTimeline from '@/components/Food/FoodTimeline.vue'
import DraggableButton from '@/components/DraggableButton.vue'
import { Search } from '@element-plus/icons-vue'
import { onMounted, reactive } from 'vue'
import { useFoodStore } from '@/store/food'
import type { IFoodSegment } from '@/types/food'
import { useRouter } from 'vue-router'
import { useAppStore } from '@/store/app'
const foodStore = useFoodStore()
const router = useRouter()
const appStore = useAppStore()
const foodInfo = reactive({
recordSegment: 'recipe' as IFoodSegment,
@@ -67,6 +74,25 @@ onMounted(() => {
const changeRecordSegmentEvent = () => {
foodStore.refreshInfo()
}
const addClick = () => {
appStore.isShowMobileBack = true
switch (foodInfo.recordSegment) {
case 'recipe':
foodStore.foodRecipeApiType = 'ADD'
foodStore.currentFoodRecipe = foodStore.getEmptyFoodRecipe()
router.push('/record/recipeForm')
break
case 'daily':
foodStore.foodRecordApiType = 'ADD'
foodStore.currentFoodRecord = foodStore.getEmptyFoodRecord()
router.push('/record/recordForm')
break
default:
break
}
}
</script>
<style scoped lang="scss">

View File

@@ -0,0 +1,99 @@
<template>
<div class="food-recipe-form common-mobile-view">
<el-form ref="formRef" :model="foodStore.currentFoodRecipe"
:rules="rules" label-width="80px">
<el-form-item label="美食名称" prop="name">
<el-input v-model="foodStore.currentFoodRecipe.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="foodStore.currentFoodRecipe.category"
placeholder="请选择或输入菜系">
<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="foodStore.currentFoodRecipe.recommendRate"
:colors="foodStore.rateColorList"/>
</el-form-item>
<el-form-item label="食材">
<food-material-form />
</el-form-item>
<el-form-item label="步骤">
<food-step-form />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="foodStore.currentFoodRecipe.remark" autocomplete="off"
placeholder="请输入备注" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
</el-form>
<div class="button-container">
<el-button type="primary" @click="confirmClick">确认</el-button>
<el-button type="danger" @click="deleteClick">删除</el-button>
<el-button type="info" @click="cancelClick">取消</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import FoodMaterialForm from '@/components/Food/FoodMaterialForm.vue'
import FoodStepForm from '@/components/Food/FoodStepForm.vue'
import { useFoodStore } from '@/store/food'
import { FormInstance, FormRules } from 'element-plus'
import { computed, ref, reactive } from 'vue'
import { foodRules } from '@/utils/element/elRules'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
const rules = reactive<FormRules>(foodRules)
const formRef = ref<FormInstance>()
const foodStore = useFoodStore()
const foodCategoryList = ['家常菜', '淮扬菜', '鲁菜', '川菜', '粤菜', '闽菜', '浙菜', '湘菜', '徽菜', '其他']
const dialogData = computed(() => foodStore.foodRecipeDialog)
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value.validate().then(() => {
foodStore.handleFoodRecipeApi(foodStore.currentFoodRecipe)
history.back()
})
}
const cancelClick = () => {
history.back()
}
const deleteClick = () => {
commonElMessageBox('是否确认删除该菜谱?').then(() => {
foodStore.foodRecipeApiType = 'DELETE'
foodStore.handleFoodRecipeApi(foodStore.currentFoodRecipe)
history.back()
})
}
</script>
<style lang="scss">
.food-recipe-form {
display: flex;
flex-direction: column;
gap: 10px;
.button-container {
align-self: center;
}
}
</style>

View File

@@ -0,0 +1,101 @@
<template>
<div class="food-record-form common-mobile-view">
<el-form ref="formRef" :model="foodStore.currentFoodRecord"
:rules="rules" label-width="80">
<el-form-item label="菜谱名称" prop="name">
<el-select
v-model="foodStore.currentFoodRecord.name"
@focus="foodStore.queryFoodNameList()"
allow-create filterable
: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="foodStore.currentFoodRecord.date"
type="date" :editable="false" placeholder="请选择完成时间"
value-format="YYYY-MM-DD"/>
</el-form-item>
<el-form-item label="完成人" prop="person">
<el-select
v-model="foodStore.currentFoodRecord.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="foodStore.currentFoodRecord.imageUrl"
:service-url="fileServiceUrl"
:image-limit="1"
:service-file-root-path="serviceFileRootPath"
:image-path="foodStore.foodImagePath"/>
</el-form-item>
</el-form>
<div class="button-container">
<el-button type="primary" @click="confirmClick">确认</el-button>
<el-button type="danger" @click="deleteClick">删除</el-button>
<el-button type="info" @click="cancelClick">取消</el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { UploadImage } from 'vue3-common'
import { useFoodStore } from '@/store/food.ts'
import { ref, reactive } from 'vue'
import { FormRules } from 'element-plus'
import { foodRecordRules } from '@/utils/element/elRules'
import { fileServiceUrl, serviceFileRootPath } from '@/utils'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
const rules = reactive<FormRules>(foodRecordRules)
const formRef = ref()
const foodStore = useFoodStore()
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value.validate().then(() => {
foodStore.handleFoodRecordApi(foodStore.currentFoodRecord)
history.back()
})
}
const cancelClick = () => {
history.back()
}
const deleteClick = () => {
commonElMessageBox('是否确认删除该记录?').then(() => {
foodStore.foodRecordApiType = 'DELETE'
foodStore.handleFoodRecordApi(foodStore.currentFoodRecord)
history.back()
})
}
</script>
<style lang="scss">
.food-record-form {
display: flex;
flex-direction: column;
gap: 10px;
.button-container {
align-self: center;
}
}
</style>

View File

@@ -20,6 +20,13 @@ export default defineConfig({
}
},
server: {
port: 5175
port: 5175,
proxy: {
'/food-service': {
target: 'http://127.0.0.1:8090',
changeOrigin: true,
rewrite: (item) => item.replace(/^\/food-service/, '')
}
}
}
})