feat:更新图片存储路径

This commit is contained in:
2026-02-01 22:34:38 +08:00
parent c0cfbbaa15
commit 7b96e60b8d
17 changed files with 295 additions and 261 deletions

View File

@@ -17,6 +17,10 @@ server {
proxy_pass http://host.docker.internal:8081/; proxy_pass http://host.docker.internal:8081/;
} }
location /alist-api/ {
proxy_pass http://host.docker.internal:5244/;
}
location /blog-api/ { location /blog-api/ {
proxy_pass http://host.docker.internal:8082/; proxy_pass http://host.docker.internal:8082/;
} }

View File

@@ -4,7 +4,7 @@
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 getFoodRecord(props.foodItem?.recordList)"
:key="index"> :key="index">
<el-image :src="serviceFileRootPath + item.imageUrl" <el-image :src="AListPath + item.imageUrl"
fit="contain" alt="暂无成果"> fit="contain" alt="暂无成果">
<template #error> <template #error>
<div class="image-slot"> <div class="image-slot">
@@ -26,8 +26,7 @@
</div> </div>
<div class="button-info"> <div class="button-info">
<el-button v-if="!isMobile()" <el-button type="primary" :icon="Edit" circle :size="'small'"
type="primary" :icon="Edit" circle :size="'small'"
@click="editFoodRecipeClick"/> @click="editFoodRecipeClick"/>
<el-button type="primary" :icon="MoreFilled" circle :size="'small'" <el-button type="primary" :icon="MoreFilled" circle :size="'small'"
@click="viewFoodRecipeClick()" /> @click="viewFoodRecipeClick()" />
@@ -70,10 +69,8 @@ import { defineProps, onMounted, PropType, reactive } from 'vue'
import { IFoodRecipe, IFoodRecord } from '@/types/food.ts' import { IFoodRecipe, IFoodRecord } 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 { commonElMessageBox } from 'vue3-common/utils/elUtil' import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { serviceFileRootPath } from '@/utils' import { AListPath } from '@/utils'
import { isMobile } from 'vue3-common/utils/layoutUtil'
const router = useRouter() const router = useRouter()
const foodStore = useFoodStore() const foodStore = useFoodStore()
@@ -102,11 +99,7 @@ const editFoodRecipeClick = async () => {
const id = props.foodItem?.id const id = props.foodItem?.id
foodStore.foodRecipeApiType = 'UPDATE' foodStore.foodRecipeApiType = 'UPDATE'
await foodStore.queryFoodRecipe(id as number) await foodStore.queryFoodRecipe(id as number)
foodStore.foodRecipeDialog = { await router.push({ name: 'FoodRecipeForm' })
title: '编辑菜谱',
visible: true,
data: deepCopyObject(foodStore.currentFoodRecipe)
}
} }
const viewFoodRecipeClick = async () => { const viewFoodRecipeClick = async () => {
@@ -116,7 +109,7 @@ const viewFoodRecipeClick = async () => {
const deleteFoodRecipeClick = () => { const deleteFoodRecipeClick = () => {
foodStore.foodRecipeApiType = 'DELETE' foodStore.foodRecipeApiType = 'DELETE'
commonElMessageBox(`请确认是否删除该菜谱: ${props.foodItem?.name}?`).then(() => { commonElMessageBox(`请确认是否删除该菜谱: ${props.foodItem?.name}?`).then(() => {
foodStore.handleFoodRecipeApi(props.foodItem?.id as number) foodStore.handleFoodRecipeApi(props.foodItem)
}) })
} }

View File

@@ -1,116 +0,0 @@
<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

@@ -1,13 +1,13 @@
<template> <template>
<el-dialog v-model="foodStore.foodMaterialDialog.visible" <el-dialog v-model="foodStore.foodMaterialDialogVisible"
:title="foodStore.foodMaterialDialog.title" :title="foodStore.foodMaterialApiType === 'ADD' ? '新增食材' : '编辑食材'"
center @open="openDialogEvent" center @open="openDialogEvent"
class="food-material-dialog"> class="food-material-dialog">
<el-form ref="formRef" :model="foodStore.foodMaterialDialog.data" <el-form ref="formRef" :model="foodStore.currentFoodMaterial"
:rules="rules"> :rules="rules" label-width="auto">
<el-form-item label="类型" prop="type"> <el-form-item label="类型" prop="type">
<el-select <el-select
v-model="foodStore.foodMaterialDialog.data.type" v-model="foodStore.currentFoodMaterial.type"
placeholder="请选择类型"> placeholder="请选择类型">
<el-option <el-option
v-for="(item, index) in foodMaterialTypeList" v-for="(item, index) in foodMaterialTypeList"
@@ -17,14 +17,14 @@
</el-form-item> </el-form-item>
<el-form-item label="名称" prop="name"> <el-form-item label="名称" prop="name">
<el-input v-model="foodStore.foodMaterialDialog.data.name" autocomplete="off" <el-input v-model="foodStore.currentFoodMaterial.name" autocomplete="off"
placeholder="请输入名称" clearable placeholder="请输入名称" clearable
show-word-limit maxlength="20"> show-word-limit maxlength="20">
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="分量" prop="amount"> <el-form-item label="分量" prop="amount">
<el-input v-model="foodStore.foodMaterialDialog.data.amount" autocomplete="off" <el-input v-model="foodStore.currentFoodMaterial.amount" autocomplete="off"
placeholder="请输入分量1斤/2勺/适量)" clearable placeholder="请输入分量1斤/2勺/适量)" clearable
show-word-limit maxlength="20"> show-word-limit maxlength="20">
</el-input> </el-input>
@@ -68,22 +68,22 @@ const confirmClick = () => {
formRef.value.validate().then(async () => { formRef.value.validate().then(async () => {
if (foodStore.foodMaterialApiType === 'UPDATE') { if (foodStore.foodMaterialApiType === 'UPDATE') {
const { editFoodMaterialIndex: index } = foodStore const { editFoodMaterialIndex: index } = foodStore
foodStore.foodRecipeDialog.data.materialList[index] = foodStore.foodMaterialDialog.data foodStore.currentFoodRecipe.materialList[index] = foodStore.currentFoodMaterial
} else { } else {
foodStore.foodRecipeDialog.data.materialList.push(foodStore.foodMaterialDialog.data) foodStore.currentFoodRecipe.materialList.push(foodStore.currentFoodMaterial)
} }
foodStore.foodMaterialDialog.visible = false foodStore.foodMaterialDialogVisible = false
}) })
} }
const cancelClick = () => { const cancelClick = () => {
foodStore.foodMaterialDialog.visible = false foodStore.foodMaterialDialogVisible = false
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.food-material-dialog { .food-material-dialog {
width: 30%; width: 80%;
.el-dialog__body { .el-dialog__body {
// height: 50vh; // height: 50vh;

View File

@@ -1,23 +1,24 @@
<template> <template>
<el-table :data="foodStore.foodRecipeDialog.data.materialList" <div class="material-form">
border stripe class="material-table"> <div style="align-self: center;">
<el-table-column type="index" align="center" width="90"> <el-button type="primary" @click="handleAddClick">新增</el-button>
<template #header> </div>
<el-button type="primary" @click="handleAddClick">新增</el-button>
</template> <el-table :data="foodStore.currentFoodRecipe.materialList"
</el-table-column> border stripe class="material-table">
<el-table-column prop="type" label="类型" align="center" min-width="10%"/> <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="name" label="名称" align="center" min-width="20%"/>
<el-table-column prop="amount" 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"> <el-table-column label="操作" align="center" width="100">
<template #default="scope"> <template #default="scope">
<el-button type="primary" text <el-button type="primary" text style="margin: 0 10px 0 0;padding: 0;"
@click="handleEditClick(scope.$index, scope.row)">编辑</el-button> @click="handleEditClick(scope.$index, scope.row)">编辑</el-button>
<el-button type="danger" text <el-button type="danger" text style="margin: 0;padding: 0;"
@click="handleDeleteClick(scope.$index, scope.row)">删除</el-button> @click="handleDeleteClick(scope.$index, scope.row)">删除</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -30,27 +31,21 @@ const foodStore = useFoodStore()
const handleAddClick = () => { const handleAddClick = () => {
foodStore.foodMaterialApiType = 'ADD' foodStore.foodMaterialApiType = 'ADD'
foodStore.foodMaterialDialog = { foodStore.currentFoodMaterial = foodStore.getEmptyFoodMaterial()
title: '新增食材', foodStore.foodMaterialDialogVisible = true
visible: true,
data: foodStore.getEmptyFoodMaterial()
}
} }
const handleEditClick = (index: number, material: IFoodMaterial) => { const handleEditClick = (index: number, material: IFoodMaterial) => {
foodStore.editFoodMaterialIndex = index foodStore.editFoodMaterialIndex = index
foodStore.foodMaterialApiType = 'UPDATE' foodStore.foodMaterialApiType = 'UPDATE'
foodStore.foodMaterialDialog = { foodStore.currentFoodMaterial = deepCopyObject(material)
title: '编辑食材', foodStore.foodMaterialDialogVisible = true
visible: true,
data: deepCopyObject(material)
}
} }
const handleDeleteClick = (index: number, material: IFoodMaterial) => { const handleDeleteClick = (index: number, material: IFoodMaterial) => {
foodStore.foodMaterialApiType = 'DELETE' foodStore.foodMaterialApiType = 'DELETE'
commonElMessageBox(`确认删除该食材: ${material.name}?`).then(() => { commonElMessageBox(`确认删除该食材: ${material.name}?`).then(() => {
foodStore.foodRecipeDialog.data.materialList.splice(index, 1) foodStore.currentFoodRecipe.materialList.splice(index, 1)
}) })
} }
</script> </script>

View File

@@ -1,19 +1,19 @@
<template> <template>
<el-dialog v-model="foodStore.foodStepDialog.visible" <el-dialog v-model="foodStore.foodStepDialogVisible"
:title="foodStore.foodStepDialog.title" :title="foodStore.foodStepApiType === 'ADD' ? '新增步骤' : '编辑步骤'"
center @open="openDialogEvent" center @open="openDialogEvent"
class="food-step-dialog"> class="food-step-dialog">
<el-form ref="formRef" :model="foodStore.foodStepDialog.data" <el-form ref="formRef" :model="foodStore.currentFoodStep"
:rules="rules"> :rules="rules" label-width="auto">
<el-form-item label="内容" prop="content"> <el-form-item label="内容" prop="content">
<el-input v-model="foodStore.foodStepDialog.data.content" autocomplete="off" <el-input v-model="foodStore.currentFoodStep.content" autocomplete="off"
placeholder="请输入内容" clearable placeholder="请输入内容" clearable
show-word-limit maxlength="20"> show-word-limit maxlength="20">
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item label="图片"> <el-form-item label="图片">
<upload-image v-model="foodStore.foodStepDialog.data.imageUrl" <upload-image v-model="foodStore.currentFoodStep.imageUrl"
:service-url="fileServiceUrl" :service-url="fileServiceUrl"
:image-limit="1" :image-limit="1"
:service-file-root-path="serviceFileRootPath" :service-file-root-path="serviceFileRootPath"
@@ -60,22 +60,22 @@ const confirmClick = () => {
.then(async () => { .then(async () => {
if (foodStore.foodStepApiType === 'UPDATE') { if (foodStore.foodStepApiType === 'UPDATE') {
const { editFoodStepIndex: index } = foodStore const { editFoodStepIndex: index } = foodStore
foodStore.foodRecipeDialog.data.stepList[index] = foodStore.foodStepDialog.data foodStore.currentFoodRecipe.stepList[index] = foodStore.currentFoodStep
} else { } else {
foodStore.foodRecipeDialog.data.stepList.push(foodStore.foodStepDialog.data) foodStore.currentFoodRecipe.stepList.push(foodStore.currentFoodStep)
} }
foodStore.foodStepDialog.visible = false foodStore.foodStepDialogVisible = false
}) })
} }
const cancelClick = () => { const cancelClick = () => {
foodStore.foodStepDialog.visible = false foodStore.foodStepDialogVisible = false
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.food-step-dialog { .food-step-dialog {
width: 30%; width: 80%;
.el-dialog__body { .el-dialog__body {
// height: 50vh; // height: 50vh;

View File

@@ -1,27 +1,28 @@
<template> <template>
<el-table :data="foodStore.foodRecipeDialog.data.stepList" <div class="step-form">
border class="food-step-table"> <div style="align-self: center;">
<el-table-column type="index" align="center" width="90"> <el-button type="primary" @click="handleAddClick">新增</el-button>
<template #header> </div>
<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"> <el-table :data="foodStore.currentFoodRecipe.stepList"
<template #default="scope"> border class="food-step-table">
<el-button type="primary" text <el-table-column prop="content" label="内容" align="center" min-width="20%" />
@click="handleEditClick(scope.$index, scope.row)">编辑</el-button> <el-table-column prop="imageUrl" label="图片" align="center" min-width="20%">
<el-button type="danger" text <template #default="scope">
@click="handleDeleteClick(scope.$index)">删除</el-button> <el-image v-if="scope.row.imageUrl" :src="serviceFileRootPath + scope.row.imageUrl" />
</template> </template>
</el-table-column> </el-table-column>
</el-table>
<el-table-column label="操作" align="center" width="100">
<template #default="scope">
<el-button type="primary" text style="margin: 0 10px 0 0;padding: 0;"
@click="handleEditClick(scope.$index, scope.row)">编辑</el-button>
<el-button type="danger" text style="margin: 0;padding: 0;"
@click="handleDeleteClick(scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -35,29 +36,22 @@ const foodStore = useFoodStore()
const handleAddClick = () => { const handleAddClick = () => {
foodStore.foodStepApiType = 'ADD' foodStore.foodStepApiType = 'ADD'
foodStore.foodStepImageList = [] foodStore.currentFoodStep = foodStore.getEmptyFoodStep()
foodStore.foodStepDialog = { foodStore.foodStepDialogVisible = true
title: '新增步骤',
visible: true,
data: foodStore.getEmptyFoodStep()
}
} }
const handleEditClick = (index: number, step: IFoodStep) => { const handleEditClick = (index: number, step: IFoodStep) => {
foodStore.editFoodStepIndex = index foodStore.editFoodStepIndex = index
foodStore.foodStepApiType = 'UPDATE' foodStore.foodStepApiType = 'UPDATE'
foodStore.foodStepDialog = { foodStore.currentFoodStep = deepCopyObject(step)
title: '编辑步骤', foodStore.foodStepDialogVisible = true
visible: true,
data: deepCopyObject(step)
}
} }
const handleDeleteClick = (index: number) => { const handleDeleteClick = (index: number) => {
commonElMessageBox('确认删除该步骤?').then(() => { commonElMessageBox('确认删除该步骤?').then(() => {
foodStore.foodStepApiType = 'DELETE' foodStore.foodStepApiType = 'DELETE'
foodStore.foodRecipeDialog.data.stepList.splice(index, 1) foodStore.currentFoodRecipe.stepList.splice(index, 1)
foodStore.foodRecipeDialog.data.stepList.forEach((item) => { foodStore.currentFoodRecipe.stepList.forEach((item) => {
if (item.sort > index) { if (item.sort > index) {
item.sort -= 1 item.sort -= 1
} }

View File

@@ -37,8 +37,8 @@
<!-- 单媒体内容图片优先于视频 --> <!-- 单媒体内容图片优先于视频 -->
<el-image <el-image
v-if="props.item?.imageUrl" v-if="props.item?.imageUrl"
:src="serviceFileRootPath + props.item?.imageUrl" :src="AListPath + props.item?.imageUrl"
:preview-src-list="[serviceFileRootPath + props.item?.imageUrl]" :preview-src-list="[AListPath + props.item?.imageUrl]"
fit="contain" fit="contain"
class="media-content"/> class="media-content"/>
@@ -46,7 +46,7 @@
v-if="props.item?.videoUrl" v-if="props.item?.videoUrl"
controls controls
class="media-content"> class="media-content">
<source :src="serviceFileRootPath + props.item?.videoUrl" type="video/mp4"> <source :src="AListPath + props.item?.videoUrl" type="video/mp4">
</video> </video>
<!-- 展开/收起按钮 --> <!-- 展开/收起按钮 -->
@@ -76,7 +76,7 @@ import { deepCopyObject } from 'vue3-common/utils/dataUtil'
import { useJournalStore } from '@/store/journal.ts' import { useJournalStore } from '@/store/journal.ts'
import { useAppStore } from '@/store/app.ts' import { useAppStore } from '@/store/app.ts'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { serviceFileRootPath } from '@/utils' import { AListPath } from '@/utils'
const props = defineProps({ const props = defineProps({
item: { item: {

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="life-record-card card-item"> <div class="life-record-card card-item">
<el-image :src="serviceFileRootPath + props.productItem?.imageUrl" <el-image :src="AListPath + props.productItem?.imageUrl"
:preview-src-list="[serviceFileRootPath + props.productItem?.imageUrl]" :preview-src-list="[AListPath + props.productItem?.imageUrl]"
fit="contain" alt="暂无成果"> fit="contain" alt="暂无成果">
<template #error> <template #error>
<div class="image-slot"> <div class="image-slot">
@@ -70,7 +70,7 @@ import { defineProps, onMounted, PropType } from 'vue'
import { commonElMessageBox } from 'vue3-common/utils/elUtil' import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { IProduct } from '@/types/product.ts' import { IProduct } from '@/types/product.ts'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { serviceFileRootPath } from '@/utils' import { AListPath } from '@/utils'
const productStore = useProductStore() const productStore = useProductStore()
const router = useRouter() const router = useRouter()

View File

@@ -12,13 +12,13 @@
<el-card class="record-card"> <el-card class="record-card">
<span class="record-content">旅行人{{ item.persons.join('-') }}</span> <span class="record-content">旅行人{{ item.persons.join('-') }}</span>
<el-image v-for="(image, index) in item.imageList" :key="index" <el-image v-for="(image, index) in item.imageList" :key="index"
:src="serviceFileRootPath + image" :src="AListPath + image"
:preview-src-list="[serviceFileRootPath + image]" :preview-src-list="[AListPath + image]"
fit="contain" alt="暂无图片" class="record-image"/> fit="contain" alt="暂无图片" class="record-image"/>
<video v-for="(video, index) in item.videoList" <video v-for="(video, index) in item.videoList"
:key="index" controls style="width: 60%;min-height: 200px;"> :key="index" controls style="width: 60%;min-height: 200px;">
<source :src="serviceFileRootPath + video" type="video/mp4"> <source :src="AListPath + video" type="video/mp4">
您的浏览器不支持 HTML5 视频 您的浏览器不支持 HTML5 视频
</video> </video>
@@ -41,7 +41,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { SvgIcon } from 'vue3-common' import { SvgIcon } from 'vue3-common'
import { useTravelStore } from '@/store/travel.ts' import { useTravelStore } from '@/store/travel.ts'
import { serviceFileRootPath } from '@/utils' import { AListPath } from '@/utils'
const travelStore = useTravelStore() const travelStore = useTravelStore()
</script> </script>

View File

@@ -3,7 +3,7 @@
<el-carousel v-if="props.travelItem?.recordList.length > 0" <el-carousel v-if="props.travelItem?.recordList.length > 0"
arrow="always" @change="changeTravelEvent"> arrow="always" @change="changeTravelEvent">
<el-carousel-item v-for="(item, index) in props.travelItem?.recordList" :key="index"> <el-carousel-item v-for="(item, index) in props.travelItem?.recordList" :key="index">
<el-image :src="serviceFileRootPath + item.imageList[0]" <el-image :src="AListPath + item.imageList[0]"
fit="contain" alt="暂无图片"> fit="contain" alt="暂无图片">
<template #error> <template #error>
<div class="image-slot"> <div class="image-slot">
@@ -70,7 +70,7 @@ import { useRouter } from 'vue-router'
import { formatDate } from 'vue3-common/utils/dateUtil' import { formatDate } from 'vue3-common/utils/dateUtil'
import { ITravelSpot } from '@/types/travel.ts' import { ITravelSpot } from '@/types/travel.ts'
import { commonElMessageBox } from 'vue3-common/utils/elUtil' import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import { serviceFileRootPath } from '@/utils' import { AListPath } from '@/utils'
import { parsePerson } from '@/utils/home/travelUtil.ts' import { parsePerson } from '@/utils/home/travelUtil.ts'
const router = useRouter() const router = useRouter()

View File

@@ -29,6 +29,16 @@ export const useFoodStore = defineStore('food', {
stepList: [], stepList: [],
recordList: [] recordList: []
} as IFoodRecipe, } as IFoodRecipe,
currentFoodMaterial: {
name: '',
type: '',
amount: ''
} as IFoodMaterial,
currentFoodStep: {
content: '',
sort: 0,
imageUrl: ''
}as IFoodStep,
foodRecordList: [] as IFoodRecord[], foodRecordList: [] as IFoodRecord[],
currentFoodRecord: { currentFoodRecord: {
id: 0, id: 0,
@@ -49,24 +59,11 @@ export const useFoodStore = defineStore('food', {
queryDateType: 'month' as IDateType, queryDateType: 'month' as IDateType,
queryDateList: [] as string[], queryDateList: [] as string[],
foodRecipeApiType: 'ADD' as IHandleApi, foodRecipeApiType: 'ADD' as IHandleApi,
foodRecipeDialog: {
visible: false,
title: '',
data: {}
} as IDialog<IFoodRecipe>,
foodMaterialApiType: 'ADD' as IHandleApi, foodMaterialApiType: 'ADD' as IHandleApi,
foodMaterialDialog: { foodMaterialDialogVisible: false,
visible: false,
title: '',
data: {}
} as IDialog<IFoodMaterial>,
editFoodMaterialIndex: 0, editFoodMaterialIndex: 0,
foodStepApiType: 'ADD' as IHandleApi, foodStepApiType: 'ADD' as IHandleApi,
foodStepDialog: { foodStepDialogVisible: false,
visible: false,
title: '',
data: {}
} as IDialog<IFoodStep>,
editFoodStepIndex: 0, editFoodStepIndex: 0,
foodRecordApiType: 'ADD' as IHandleApi, foodRecordApiType: 'ADD' as IHandleApi,
foodRecordDialog: { foodRecordDialog: {
@@ -123,25 +120,23 @@ export const useFoodStore = defineStore('food', {
async queryFoodCategory() { async queryFoodCategory() {
this.foodCategoryList = await queryFoodCategoryApi() this.foodCategoryList = await queryFoodCategoryApi()
}, },
async handleFoodRecipeApi(id?: number) { async handleFoodRecipeApi(recipe: IFoodRecipe) {
const { data } = this.foodRecipeDialog
switch (this.foodRecipeApiType) { switch (this.foodRecipeApiType) {
case 'ADD': case 'ADD':
await addFoodRecipeApi(data) await addFoodRecipeApi(recipe)
ElMessage.success('新增菜谱成功') ElMessage.success('新增菜谱成功')
break break
case 'UPDATE': case 'UPDATE':
await updateFoodRecipeApi(data.id as number, data) await updateFoodRecipeApi(recipe.id as number, recipe)
ElMessage.success('更新菜谱成功') ElMessage.success('更新菜谱成功')
break break
case 'DELETE': case 'DELETE':
await deleteFoodRecipeApi(id as number) await deleteFoodRecipeApi(recipe.id as number)
ElMessage.success('删除菜谱成功') ElMessage.success('删除菜谱成功')
break break
default: default:
break break
} }
this.foodRecipeDialog.visible = false
await this.refreshInfo() await this.refreshInfo()
}, },
async handleFoodRecordApi(foodRecord: IFoodRecord) { async handleFoodRecordApi(foodRecord: IFoodRecord) {

View File

@@ -28,6 +28,8 @@ export const fileServiceUrl = '/home-api/file'
*/ */
export const serviceFileRootPath = '/home-api/' export const serviceFileRootPath = '/home-api/'
export const AListPath = '/alist-api/'
/** /**
* 获取assets静态资源 * 获取assets静态资源
* @param url 路径 * @param url 路径

View File

@@ -0,0 +1,132 @@
<template>
<div class="mobile-food-recipe-form common-mobile-view">
<el-form ref="formRef" :model="foodStore.currentFoodRecipe"
:rules=rules label-width="80px">
<el-steps :active="activeStep" align-center
style="margin-bottom: 10px;" >
<el-step title="基础信息" />
<el-step title="Step 2" />
<el-step title="Step 3" />
</el-steps>
<div v-if="activeStep === 0">
<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">
<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="备注">
<el-input v-model="foodStore.currentFoodRecipe.remark" autocomplete="off"
placeholder="请输入备注" clearable
show-word-limit maxlength="20">
</el-input>
</el-form-item>
</div>
<div v-if="activeStep === 1">
<food-material-form />
</div>
<div v-if="activeStep === 2">
<food-step-form />
</div>
</el-form>
<div class="button-container">
<el-button type="primary" :disabled="activeStep === 0"
@click="previewClick">上一步</el-button>
<el-button type="primary" :disabled="activeStep === 2"
@click="nextClick">下一步</el-button>
</div>
<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>
<food-material-dialog />
<food-step-dialog />
</div>
</template>
<script lang="ts" setup>
import FoodMaterialDialog from '@/components/Food/Form/FoodMaterialDialog.vue'
import FoodStepDialog from '@/components/Food/Form/FoodStepDialog.vue'
import { useFoodStore } from '@/store/food.ts'
import { ref, reactive } from 'vue'
import { FormRules } from 'element-plus'
import { foodRecordRules } from '@/utils/element/elRules.ts'
import { commonElMessageBox } from 'vue3-common/utils/elUtil'
import FoodMaterialForm from '@/components/Food/Form/FoodMaterialForm.vue'
import FoodStepForm from '@/components/Food/Form/FoodStepForm.vue'
const rules = reactive<FormRules>(foodRecordRules)
const formRef = ref()
const activeStep = ref(0)
const foodStore = useFoodStore()
const foodCategoryList = ['家常菜', '淮扬菜', '鲁菜', '川菜', '粤菜', '闽菜', '浙菜', '湘菜', '徽菜', '其他']
const previewClick = () => {
activeStep.value--
}
const nextClick = () => {
activeStep.value++
}
/**
* 点击确认按钮
*/
const confirmClick = () => {
formRef.value.validate().then(() => {
foodStore.handleFoodRecipeApi(foodStore.currentFoodRecipe)
history.back()
})
}
const cancelClick = () => {
history.back()
}
const deleteClick = () => {
commonElMessageBox('是否确认删除该记录?').then(() => {
foodStore.foodRecordApiType = 'DELETE'
foodStore.handleFoodRecipeApi(foodStore.currentFoodRecipe)
history.back()
})
}
</script>
<style lang="scss">
.mobile-food-recipe-form {
display: flex;
flex-direction: column;
gap: 10px;
.button-container {
align-self: center;
}
.material-form, .step-form {
display: flex;
flex-direction: column;
gap: 10px;
}
}</style>

View File

@@ -18,6 +18,10 @@
title="账单记录" sub-title="收支一目了然" title="账单记录" sub-title="收支一目了然"
router="/bill/data" @click="initBillClick"/> router="/bill/data" @click="initBillClick"/>
<module-card icon="iconfont icon-canyin" color="#FF8C32"
title="美食记录" sub-title="收录每日餐食"
router="/food/recipe"/>
<module-card icon="iconfont icon-jingdian" color="#FF9966" <module-card icon="iconfont icon-jingdian" color="#FF9966"
title="旅行记录" sub-title="足迹遍及四方" title="旅行记录" sub-title="足迹遍及四方"
router="/travel/spot"/> router="/travel/spot"/>

View File

@@ -27,10 +27,36 @@ export const mobileHomeMeta: IRouteMetaConfig = {
title: '账单设置', title: '账单设置',
icon: '' icon: ''
}, },
'/food': {
title: '美食记录',
icon: '',
order: 2,
redirect: '/food/recipe'
},
'/food/recipe': {
title: '菜谱',
icon: ''
},
'/food/record': {
title: '记录',
icon: ''
},
'/food/detail/:id': {
title: '菜谱内容',
icon: ''
},
'/food/recipeForm': {
title: '菜谱表单',
icon: ''
},
'/food/recordForm': {
title: '记录表单',
icon: ''
},
'/travel': { '/travel': {
title: '旅行记录', title: '旅行记录',
icon: '', icon: '',
order: 2, order: 3,
redirect: '/travel/spot' redirect: '/travel/spot'
}, },
'/travel/spot': { '/travel/spot': {
@@ -56,7 +82,7 @@ export const mobileHomeMeta: IRouteMetaConfig = {
'/health': { '/health': {
title: '运动健康', title: '运动健康',
icon: '', icon: '',
order: 3, order: 4,
redirect: '/health/calendar' redirect: '/health/calendar'
}, },
'/health/calendar': { '/health/calendar': {
@@ -70,7 +96,7 @@ export const mobileHomeMeta: IRouteMetaConfig = {
'/anniversary': { '/anniversary': {
title: '纪念日', title: '纪念日',
icon: 'home-anniversary', icon: 'home-anniversary',
order: 4, order: 5,
redirect: '/anniversary/list' redirect: '/anniversary/list'
}, },
'/anniversary/list': { '/anniversary/list': {
@@ -88,7 +114,7 @@ export const mobileHomeMeta: IRouteMetaConfig = {
'/period': { '/period': {
title: '月经记录', title: '月经记录',
icon: 'home-period', icon: 'home-period',
order: 5, order: 6,
redirect: '/period/calendar' redirect: '/period/calendar'
}, },
'/period/calendar': { '/period/calendar': {
@@ -106,7 +132,7 @@ export const mobileHomeMeta: IRouteMetaConfig = {
'/product': { '/product': {
title: '物品记录', title: '物品记录',
icon: 'home-product', icon: 'home-product',
order: 6, order: 7,
redirect: '/product/list' redirect: '/product/list'
}, },
'/product/list': { '/product/list': {
@@ -120,7 +146,7 @@ export const mobileHomeMeta: IRouteMetaConfig = {
'/journal': { '/journal': {
title: '日记', title: '日记',
icon: 'home-product', icon: 'home-product',
order: 7, order: 8,
redirect: '/journal/record' redirect: '/journal/record'
}, },
'/journal/record': { '/journal/record': {
@@ -138,7 +164,7 @@ export const mobileHomeMeta: IRouteMetaConfig = {
'/kpi': { '/kpi': {
title: '绩效', title: '绩效',
icon: 'home-kpi', icon: 'home-kpi',
order: 8, order: 9,
redirect: '/kpi/record' redirect: '/kpi/record'
}, },
'/kpi/record': { '/kpi/record': {
@@ -152,7 +178,7 @@ export const mobileHomeMeta: IRouteMetaConfig = {
'/asset': { '/asset': {
title: '资产', title: '资产',
icon: 'home-asset', icon: 'home-asset',
order: 9, order: 10,
redirect: '/asset/list' redirect: '/asset/list'
}, },
'/asset/list': { '/asset/list': {
@@ -166,7 +192,7 @@ export const mobileHomeMeta: IRouteMetaConfig = {
'/ledger': { '/ledger': {
title: '账本', title: '账本',
icon: 'home-ledger', icon: 'home-ledger',
order: 10, order: 11,
redirect: '/ledger/list' redirect: '/ledger/list'
}, },
'/ledger/list': { '/ledger/list': {
@@ -184,7 +210,7 @@ export const mobileHomeMeta: IRouteMetaConfig = {
'/password': { '/password': {
title: '密码', title: '密码',
icon: 'home-password', icon: 'home-password',
order: 11, order: 12,
redirect: '/password/list' redirect: '/password/list'
}, },
'/password/list': { '/password/list': {
@@ -194,7 +220,7 @@ export const mobileHomeMeta: IRouteMetaConfig = {
'/profile': { '/profile': {
title: '个人信息', title: '个人信息',
icon: '', icon: '',
order: 12, order: 13,
hidden: true, hidden: true,
redirect: '/profile/index' redirect: '/profile/index'
}, },

View File

@@ -50,6 +50,11 @@ export default defineConfig({
target: `http://${API_HOST}:8082`, target: `http://${API_HOST}:8082`,
changeOrigin: true, changeOrigin: true,
rewrite: (item) => item.replace(/^\/blog-api/, '') rewrite: (item) => item.replace(/^\/blog-api/, '')
},
'/alist-api': {
target: 'http://192.168.1.7:5244',
changeOrigin: true,
rewrite: (item) => item.replace(/^\/alist-api/, '')
} }
} }
} }