|
|
@@ -1,992 +1,1120 @@
|
|
|
<template>
|
|
|
- <div class="container" @keydown.enter.prevent>
|
|
|
- <!-- 中间卡片 -->
|
|
|
- <div class="add-hospital-wrapper">
|
|
|
- <div class="header-bar">
|
|
|
- <el-button @click="goBack" type="default" icon="ArrowLeft" class="back-btn">返回</el-button>
|
|
|
- <span class="title">院内膳食</span>
|
|
|
- </div>
|
|
|
- <div class="form-bar">
|
|
|
- <el-row :gutter="20" align="middle">
|
|
|
- <el-col :span="3">
|
|
|
- <span>配餐日期:</span>
|
|
|
- </el-col>
|
|
|
- <el-col :span="8">
|
|
|
- <el-date-picker v-model="mealTimeRange" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" style="width: 100%" @keydown.enter.prevent />
|
|
|
- </el-col>
|
|
|
- <el-col :span="3">
|
|
|
- <el-button type="primary" @click.prevent="addRecipe">新增食谱</el-button>
|
|
|
- </el-col>
|
|
|
- <el-col :span="3">
|
|
|
- <el-button @click.prevent="openRecipeTemplate">食谱模板</el-button>
|
|
|
- </el-col>
|
|
|
- <el-col :span="3">
|
|
|
- <el-button type="danger" @click.prevent="deleteAllRecipes">全部删除</el-button>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </div>
|
|
|
- <div class="tab-bar">
|
|
|
- <el-button v-for="(recipe, index) in allTableData" :key="index" :type="currentRecipe === index ? 'success' : 'info'" plain icon="Document" class="tab-btn" :class="{active: currentRecipe === index}" @click="switchRecipe(index)">
|
|
|
- 食谱{{ index + 1 }}
|
|
|
- <el-icon class="close-icon" @click.stop="deleteRecipe(index)" v-if="allTableData.length > 1">
|
|
|
-
|
|
|
- <Close />
|
|
|
+ <div class="container" @keydown.enter.prevent>
|
|
|
+ <!-- 中间卡片 -->
|
|
|
+ <div class="add-hospital-wrapper">
|
|
|
+ <div class="header-bar">
|
|
|
+ <el-button @click="goBack" type="default" icon="ArrowLeft" class="back-btn">返回</el-button>
|
|
|
+ <span class="title">院内膳食</span>
|
|
|
+ </div>
|
|
|
+ <div class="form-bar">
|
|
|
+ <el-row :gutter="20" align="middle">
|
|
|
+ <el-col :span="3">
|
|
|
+ <span>配餐日期:</span>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="mealTimeRange"
|
|
|
+ type="daterange"
|
|
|
+ range-separator="-"
|
|
|
+ start-placeholder="开始日期"
|
|
|
+ end-placeholder="结束日期"
|
|
|
+ style="width: 100%"
|
|
|
+ @keydown.enter.prevent
|
|
|
+ />
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="3">
|
|
|
+ <el-button type="primary" @click.prevent="addRecipe">新增食谱</el-button>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="3">
|
|
|
+ <el-button @click.prevent="openRecipeTemplate">食谱模板</el-button>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="3">
|
|
|
+ <el-button type="danger" @click.prevent="deleteAllRecipes">全部删除</el-button>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </div>
|
|
|
+ <div class="tab-bar">
|
|
|
+ <el-button
|
|
|
+ v-for="(recipe, index) in allTableData"
|
|
|
+ :key="index"
|
|
|
+ :type="currentRecipe === index ? 'success' : 'info'"
|
|
|
+ plain
|
|
|
+ icon="Document"
|
|
|
+ class="tab-btn"
|
|
|
+ :class="{active: currentRecipe === index}"
|
|
|
+ @click="switchRecipe(index)"
|
|
|
+ >
|
|
|
+ 食谱{{ index + 1 }}
|
|
|
+ <el-icon class="close-icon" @click.stop="deleteRecipe(index)" v-if="allTableData.length > 1">
|
|
|
+ <Close />
|
|
|
+ </el-icon>
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <el-alert
|
|
|
+ title="周食谱模板的食谱数量小于7时,食谱可循环使用"
|
|
|
+ type="warning"
|
|
|
+ show-icon
|
|
|
+ class="mb-2 info-alert"
|
|
|
+ :closable="false"
|
|
|
+ style="background: transparent; border: none; color: #ff9900"
|
|
|
+ />
|
|
|
+
|
|
|
+ <el-table :data="tableData" class="diet-table" border>
|
|
|
+ <el-table-column prop="meal" label="餐次" width="200px" align="center" />
|
|
|
+ <el-table-column prop="time" label="用餐时间" width="260px" align="center">
|
|
|
+ <template #default="{row}">
|
|
|
+ <el-time-select v-model="row.time" start="06:00" step="00:30" end="22:00" placeholder="选择时间" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="menuName" label="食谱名称" width="350px" align="center">
|
|
|
+ <template #default="{row}">
|
|
|
+ <div class="recipe-list">
|
|
|
+ <div v-if="!row.recipes?.length" class="recipe-input" @click.stop.prevent="openRecipeDialog(row)">请选择</div>
|
|
|
+ <template v-else>
|
|
|
+ <div v-for="(recipe, index) in row.recipes" :key="recipe.id" class="recipe-item">
|
|
|
+ <div class="recipe-input" @click.stop.prevent="openRecipeDialog(row, index)">
|
|
|
+ {{ recipe.name }}
|
|
|
+ </div>
|
|
|
+ <el-button type="danger" link class="delete-btn" @click.stop="removeRecipe(row, index)">
|
|
|
+ <el-icon>
|
|
|
+ <Delete />
|
|
|
</el-icon>
|
|
|
- </el-button>
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <!-- <div class="recipe-input add-more" @click.stop.prevent="openRecipeDialog(row)">请选择</div> -->
|
|
|
+ </template>
|
|
|
</div>
|
|
|
- <el-alert title="周食谱模板的食谱数量小于7时,食谱可循环使用" type="warning" show-icon class="mb-2 info-alert" :closable="false" style="background: transparent; border: none; color: #ff9900" />
|
|
|
-
|
|
|
- <el-table :data="tableData" class="diet-table" border>
|
|
|
-
|
|
|
- <el-table-column prop="meal" label="餐次" width="200px" align="center" />
|
|
|
- <el-table-column prop="time" label="用餐时间" width="260px" align="center">
|
|
|
- <template #default="{row}">
|
|
|
- <el-time-select v-model="row.time" start="06:00" step="00:30" end="22:00" placeholder="选择时间" />
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="menuName" label="食谱名称" width="350px" align="center">
|
|
|
- <template #default="{row}">
|
|
|
- <div class="recipe-list">
|
|
|
- <div v-if="!row.recipes?.length" class="recipe-input" @click.stop.prevent="openRecipeDialog(row)">请选择</div>
|
|
|
- <template v-else>
|
|
|
- <div v-for="(recipe, index) in row.recipes" :key="recipe.id" class="recipe-item">
|
|
|
- <div class="recipe-input" @click.stop.prevent="openRecipeDialog(row, index)">
|
|
|
- {{ recipe.name }}
|
|
|
- </div>
|
|
|
- <el-button type="danger" link class="delete-btn" @click.stop="removeRecipe(row, index)">
|
|
|
- <el-icon>
|
|
|
- <Delete />
|
|
|
- </el-icon>
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
- <!-- <div class="recipe-input add-more" @click.stop.prevent="openRecipeDialog(row)">请选择</div> -->
|
|
|
- </template>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="categoryName" label="食谱分类" width="220px" align="center">
|
|
|
- <template #default="{row}">
|
|
|
- <div v-for="(recipe) in row.recipes" :key="recipe.id" class="recipe-item">
|
|
|
- <div class="recipe-input-one">
|
|
|
- {{ recipe.categoryName }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="count" label="份数" width="220px" align="center">
|
|
|
- <template #default="{row}">
|
|
|
- <el-input-number v-model="row.count" :min="1" :max="99" @change="updateRowData(row)" size="small" />
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <!-- <el-table-column prop="weight" label="食材重量" align="center">
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="categoryName" label="食谱分类" width="220px" align="center">
|
|
|
+ <template #default="{row}">
|
|
|
+ <div v-for="recipe in row.recipes" :key="recipe.id" class="recipe-item">
|
|
|
+ <div class="recipe-input-one">
|
|
|
+ {{ recipe.categoryName }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="count" label="份数" width="220px" align="center">
|
|
|
+ <template #default="{row}">
|
|
|
+ <el-input-number v-model="row.count" :min="1" :max="99" @change="updateRowData(row)" size="small" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <!-- <el-table-column prop="weight" label="食材重量" align="center">
|
|
|
<template #default="{row}">
|
|
|
<span>{{ row.weight || '-' }}</span>
|
|
|
</template>
|
|
|
</el-table-column> -->
|
|
|
- <el-table-column label="食材重量" align="center">
|
|
|
- <template #default="{ row }">
|
|
|
- <div v-if="row.recipes && row.recipes.length">
|
|
|
- <div v-for="(recipe) in row.recipes" :key="recipe.id">
|
|
|
- {{ getRecipeWeight(recipe,row.count) }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <span v-else>-</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="calorie" label="热量" align="center">
|
|
|
- <template #default="{row}">
|
|
|
- <span>{{ row.calorie }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="amount" label="金额" align="center">
|
|
|
- <template #default="{row}">
|
|
|
- <span>{{ row.amount || '0.00' }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
-
|
|
|
- <div class="footer-bar">
|
|
|
- <span>费用:</span>
|
|
|
- <span class="amount">0.00元</span>
|
|
|
+ <el-table-column label="食材重量" align="center">
|
|
|
+ <template #default="{row}">
|
|
|
+ <div v-if="row.recipes && row.recipes.length">
|
|
|
+ <div v-for="recipe in row.recipes" :key="recipe.id">
|
|
|
+ {{ getRecipeWeight(recipe, row.count) }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="calorie" label="热量" align="center">
|
|
|
+ <template #default="{row}">
|
|
|
+ <div v-if="row.recipes && row.recipes.length">
|
|
|
+ <div v-for="recipe in row.recipes" :key="recipe.id">
|
|
|
+ {{ getRecipeCalorie(recipe, row.calorie) }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
- <!-- 饼状图表卡片 -->
|
|
|
- <el-card>
|
|
|
- <el-tabs v-model="activeTab" class="mt-4">
|
|
|
- <el-tab-pane label="三大营养分析" name="nutrition">
|
|
|
- <div class="nutrition-analysis">
|
|
|
- <div class="nutrition-header">
|
|
|
- <div class="nutrition-target">
|
|
|
- <span>营养设定:</span>
|
|
|
- <span class="value">{{ nutritionTarget }}kcal/d</span>
|
|
|
- </div>
|
|
|
- <div class="nutrition-actual">
|
|
|
- <span>实际热量:</span>
|
|
|
- <span class="value">{{ actualCalories }}kcal/d</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <el-row :gutter="24">
|
|
|
- <el-col :span="14">
|
|
|
- <el-table :data="nutritionData" border class="mt-4">
|
|
|
- <el-table-column prop="name" label="三大营养素" width="180" />
|
|
|
- <el-table-column prop="weight" label="质量(g)" width="180" />
|
|
|
- <el-table-column prop="ratio" label="热量占比">
|
|
|
- <template #default="{row}">
|
|
|
- <span>{{ row.ratio }}%</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="reference" label="参考值">
|
|
|
- <template #default="{row}">
|
|
|
- <span>{{ row.reference }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
- </el-col>
|
|
|
-
|
|
|
- <el-col :span="10">
|
|
|
- <div class="chart-container">
|
|
|
- <div class="chart-title">三大营养素元素质量占比</div>
|
|
|
- <div id="nutritionChart" class="chart-content"></div>
|
|
|
- <div class="chart-legend">
|
|
|
- <span class="legend-item">
|
|
|
- <span class="color-block protein"></span>
|
|
|
- 蛋白质 {{ proteinRatio }}%
|
|
|
- </span>
|
|
|
- <span class="legend-item">
|
|
|
- <span class="color-block fat"></span>
|
|
|
- 脂肪 {{ fatRatio }}%
|
|
|
- </span>
|
|
|
- <span class="legend-item">
|
|
|
- <span class="color-block carbs"></span>
|
|
|
- 碳水化合物 {{ carbsRatio }}%
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </div>
|
|
|
- </el-tab-pane>
|
|
|
-
|
|
|
- <el-tab-pane label="营养数据分析" name="data">
|
|
|
- <!-- 营养数据分析内容 -->
|
|
|
- </el-tab-pane>
|
|
|
- </el-tabs>
|
|
|
- </el-card>
|
|
|
-
|
|
|
- <!-- 底部按钮卡片 -->
|
|
|
- <div class="bottom-btn-card">
|
|
|
- <div class="btn-wrapper">
|
|
|
- <el-button type="info" plain @click="saveDraft">存为草稿</el-button>
|
|
|
- <el-button type="primary" @click="handleSubmit">提交</el-button>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="金额" align="center">
|
|
|
+ <template #default="{row}">
|
|
|
+ <div v-if="row.recipes && row.recipes.length">
|
|
|
+ <div v-for="recipe in row.recipes" :key="recipe.id">
|
|
|
+ {{ getRecipeAmount(recipe, row.count) }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
+ <span v-else>-</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <div class="footer-bar">
|
|
|
+ <span>费用:</span>
|
|
|
+ <span class="amount">{{ totalPrice }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部按钮卡片 -->
|
|
|
+ <div class="bottom-btn-card">
|
|
|
+ <div class="btn-wrapper">
|
|
|
+ <el-button type="info" plain @click="saveDraft">存为草稿</el-button>
|
|
|
+ <el-button type="primary" @click="handleSubmit">提交</el-button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
|
|
|
- <!-- 添加弹窗组件 -->
|
|
|
- <CommonDialog ref="dialogRef" @confirm="handleRecipeConfirm" @cancel="handleRecipeCancel" />
|
|
|
- <!-- 食谱模板弹窗组件 -->
|
|
|
- <RecipeTemplate ref="recipeTemplateRef" @confirm="handleRecipeTemplateConfirm" />
|
|
|
+ <!-- 添加弹窗组件 -->
|
|
|
+ <CommonDialog ref="dialogRef" @confirm="handleRecipeConfirm" @cancel="handleRecipeCancel" />
|
|
|
+ <!-- 食谱模板弹窗组件 -->
|
|
|
+ <RecipeTemplate ref="recipeTemplateRef" @confirm="handleRecipeTemplateConfirm" />
|
|
|
</template>
|
|
|
<script setup lang="ts">
|
|
|
- import { ref, onMounted, computed, watch } from 'vue';
|
|
|
- import * as echarts from 'echarts';
|
|
|
- import CommonDialog from './components/CommonDialog.vue';
|
|
|
- import RecipeTemplate from './components/RecipeTemplate.vue';
|
|
|
- const emit = defineEmits(['goBack', 'submitHospital']);
|
|
|
- import { MealRecipeForm } from '@/api/patients/hospitalMealRecipe/types';
|
|
|
- import { ElMessage } from 'element-plus';
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- const props = defineProps({
|
|
|
- patientInfo: {
|
|
|
- type: Object,
|
|
|
- required: true,
|
|
|
- default: () => ({
|
|
|
- id: '',
|
|
|
- name: '',
|
|
|
- age: '',
|
|
|
- gender: ''
|
|
|
- })
|
|
|
- }
|
|
|
+import {ref, onMounted, computed, watch, nextTick} from 'vue';
|
|
|
+import {MealPlanVO} from '@/api/patients/hospitalMealPlan/types';
|
|
|
+
|
|
|
+import * as echarts from 'echarts';
|
|
|
+import CommonDialog from './components/CommonDialog.vue';
|
|
|
+import RecipeTemplate from './components/RecipeTemplate.vue';
|
|
|
+const emit = defineEmits(['goBack', 'submitHospital']);
|
|
|
+import {MealRecipeForm} from '@/api/patients/hospitalMealRecipe/types';
|
|
|
+import {ElMessage} from 'element-plus';
|
|
|
+import {useHospitalStore} from '@/store/modules/hospital';
|
|
|
+
|
|
|
+const useStore = useHospitalStore();
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ patientInfo: {
|
|
|
+ type: Object,
|
|
|
+ required: true,
|
|
|
+ default: () => ({
|
|
|
+ id: '',
|
|
|
+ name: '',
|
|
|
+ age: '',
|
|
|
+ gender: ''
|
|
|
+ })
|
|
|
+ },
|
|
|
+ rowData: {
|
|
|
+ type: Object as PropType<MealPlanVO | null>,
|
|
|
+ default: null
|
|
|
+ }
|
|
|
+});
|
|
|
+const DRAFT_KEY = computed(() => 'addHospitalDraft_' + props.patientInfo.id);
|
|
|
+interface Recipe {
|
|
|
+ id: number;
|
|
|
+ name: string;
|
|
|
+ categoryId: number;
|
|
|
+ categoryName: string;
|
|
|
+ ingredients: string;
|
|
|
+ selected?: boolean;
|
|
|
+ weight?: string; // 添加可选的weight属性
|
|
|
+ calorie?: string; // 添加可选的calorie属性
|
|
|
+}
|
|
|
+
|
|
|
+interface Category {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+}
|
|
|
+
|
|
|
+interface TableRow {
|
|
|
+ meal: string;
|
|
|
+ time: string;
|
|
|
+ foodId: string;
|
|
|
+ menuName: string;
|
|
|
+ categoryId: string;
|
|
|
+ categoryName: string;
|
|
|
+ count: number;
|
|
|
+ weight: string;
|
|
|
+ calorie: string;
|
|
|
+ amount: string;
|
|
|
+ recipes?: Recipe[];
|
|
|
+}
|
|
|
+
|
|
|
+// 定义食谱模板数据结构
|
|
|
+interface RecipeTemplateItem {
|
|
|
+ name: string;
|
|
|
+ categoryId: number;
|
|
|
+ categoryName: string;
|
|
|
+ meals: Array<{
|
|
|
+ meal: string;
|
|
|
+ dish: string;
|
|
|
+ weight: string;
|
|
|
+ kcal: string;
|
|
|
+ }>;
|
|
|
+}
|
|
|
+
|
|
|
+const mealTimeRange = ref([new Date(), new Date()]);
|
|
|
+const activeTab = ref('nutrition');
|
|
|
+const totalPrice = ref(0.0);
|
|
|
+
|
|
|
+// 计算总价格
|
|
|
+const calculateTotalPrice = () => {
|
|
|
+ let total = 0;
|
|
|
+ allTableData.value.forEach((tableData) => {
|
|
|
+ tableData.forEach((row) => {
|
|
|
+ // 直接使用行的总金额,因为 row.amount 已经包含了该行所有食谱的总金额
|
|
|
+ if (row.amount) {
|
|
|
+ total += parseFloat(row.amount) || 0;
|
|
|
+ }
|
|
|
});
|
|
|
- const DRAFT_KEY = computed(() => 'addHospitalDraft_' + props.patientInfo.id);
|
|
|
- interface Recipe {
|
|
|
- id: number;
|
|
|
- name: string;
|
|
|
- categoryId: number;
|
|
|
- categoryName: string;
|
|
|
- ingredients: string;
|
|
|
- selected ? : boolean;
|
|
|
- weight ? : string; // 添加可选的weight属性
|
|
|
- calorie ? : string; // 添加可选的calorie属性
|
|
|
+ });
|
|
|
+ totalPrice.value = total;
|
|
|
+};
|
|
|
+
|
|
|
+const nutritionData = ref([
|
|
|
+ {name: '蛋白质', weight: '', ratio: 0, reference: '10% - 15%'},
|
|
|
+ {name: '脂肪', weight: '', ratio: 0, reference: '20% - 30%'},
|
|
|
+ {name: '碳水化合物', weight: '', ratio: 0, reference: '50% - 65%'}
|
|
|
+]);
|
|
|
+
|
|
|
+const initialTableData: TableRow[] = [
|
|
|
+ {
|
|
|
+ meal: '早餐',
|
|
|
+ time: '08:00',
|
|
|
+ foodId: '',
|
|
|
+ menuName: '',
|
|
|
+ categoryId: '',
|
|
|
+ categoryName: '',
|
|
|
+ count: 1,
|
|
|
+ weight: '',
|
|
|
+ calorie: '',
|
|
|
+ amount: '',
|
|
|
+ recipes: []
|
|
|
+ },
|
|
|
+ {
|
|
|
+ meal: '早加',
|
|
|
+ time: '08:30',
|
|
|
+ foodId: '',
|
|
|
+ menuName: '',
|
|
|
+ categoryId: '',
|
|
|
+ categoryName: '',
|
|
|
+ count: 1,
|
|
|
+ weight: '',
|
|
|
+ calorie: '',
|
|
|
+ amount: '',
|
|
|
+ recipes: []
|
|
|
+ },
|
|
|
+ {
|
|
|
+ meal: '中餐',
|
|
|
+ time: '12:00',
|
|
|
+ foodId: '',
|
|
|
+ menuName: '',
|
|
|
+ categoryId: '',
|
|
|
+ categoryName: '',
|
|
|
+ count: 1,
|
|
|
+ weight: '',
|
|
|
+ calorie: '',
|
|
|
+ amount: '',
|
|
|
+ recipes: []
|
|
|
+ },
|
|
|
+ {
|
|
|
+ meal: '中加',
|
|
|
+ time: '12:30',
|
|
|
+ foodId: '',
|
|
|
+ menuName: '',
|
|
|
+ categoryId: '',
|
|
|
+ categoryName: '',
|
|
|
+ count: 1,
|
|
|
+ weight: '',
|
|
|
+ calorie: '',
|
|
|
+ amount: '',
|
|
|
+ recipes: []
|
|
|
+ },
|
|
|
+ {
|
|
|
+ meal: '晚餐',
|
|
|
+ time: '18:00',
|
|
|
+ foodId: '',
|
|
|
+ menuName: '',
|
|
|
+ categoryId: '',
|
|
|
+ categoryName: '',
|
|
|
+ count: 1,
|
|
|
+ weight: '',
|
|
|
+ calorie: '',
|
|
|
+ amount: '',
|
|
|
+ recipes: []
|
|
|
+ },
|
|
|
+ {
|
|
|
+ meal: '晚加',
|
|
|
+ time: '18:30',
|
|
|
+ foodId: '',
|
|
|
+ menuName: '',
|
|
|
+ categoryId: '',
|
|
|
+ categoryName: '',
|
|
|
+ count: 1,
|
|
|
+ weight: '',
|
|
|
+ calorie: '',
|
|
|
+ amount: '',
|
|
|
+ recipes: []
|
|
|
+ }
|
|
|
+];
|
|
|
+
|
|
|
+const allTableData = ref<TableRow[][]>([JSON.parse(JSON.stringify(initialTableData))]);
|
|
|
+const currentRecipe = ref(0);
|
|
|
+const tableData = computed({
|
|
|
+ get: () => allTableData.value[currentRecipe.value],
|
|
|
+ set: (val) => {
|
|
|
+ allTableData.value[currentRecipe.value] = val;
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const recipes = ref([0]); // 食谱列表
|
|
|
+
|
|
|
+// 添加食谱
|
|
|
+// 新增食谱,最多7个
|
|
|
+const addRecipe = () => {
|
|
|
+ if (allTableData.value.length >= 7) {
|
|
|
+ openRecipeTemplate();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ allTableData.value.push(JSON.parse(JSON.stringify(initialTableData)));
|
|
|
+ currentRecipe.value = allTableData.value.length - 1;
|
|
|
+};
|
|
|
+
|
|
|
+function convertTableDataToMealRecipeList(tableData: TableRow[], tabNo: number): MealRecipeForm[] {
|
|
|
+ const result: MealRecipeForm[] = [];
|
|
|
+ tableData.forEach((row) => {
|
|
|
+ if (row.recipes && row.recipes.length > 0) {
|
|
|
+ row.recipes.forEach((recipe) => {
|
|
|
+ // 计算单个食谱的重量并乘以份数
|
|
|
+ const singleWeight = parseRecipeWeight(recipe);
|
|
|
+ result.push({
|
|
|
+ recipeNo: tabNo,
|
|
|
+ mealTime: row.meal,
|
|
|
+ eatTime: row.time,
|
|
|
+ foodId: recipe.id,
|
|
|
+ foodName: recipe.name,
|
|
|
+ foodCategoryId: recipe.categoryId,
|
|
|
+ foodWeight: singleWeight * row.count, // 这里乘以份数
|
|
|
+ calorie: recipe.calorie ? Number(recipe.calorie) : undefined,
|
|
|
+ amount: row.count,
|
|
|
+ foodCategoryName: recipe.categoryName,
|
|
|
+ price: row.amount ? Number(row.amount) : undefined
|
|
|
+ });
|
|
|
+ });
|
|
|
}
|
|
|
-
|
|
|
- interface Category {
|
|
|
- id: string;
|
|
|
- name: string;
|
|
|
+ });
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+function getRecipeWeight(recipe, count = 1) {
|
|
|
+ let weight = 0;
|
|
|
+ if (recipe.weight) {
|
|
|
+ weight = parseFloat(recipe.weight.replace(/[^\d.]/g, '') || '0');
|
|
|
+ } else if (recipe.ingredients) {
|
|
|
+ const matches = recipe.ingredients.match(/([\d.]+)g/g);
|
|
|
+ if (matches) {
|
|
|
+ weight = matches.reduce((sum, item) => {
|
|
|
+ const num = parseFloat(item.replace('g', ''));
|
|
|
+ return sum + (isNaN(num) ? 0 : num);
|
|
|
+ }, 0);
|
|
|
}
|
|
|
-
|
|
|
- interface TableRow {
|
|
|
- meal: string;
|
|
|
- time: string;
|
|
|
- foodId: string;
|
|
|
- menuName: string;
|
|
|
- categoryId: string;
|
|
|
- categoryName: string;
|
|
|
- count: number;
|
|
|
- weight: string;
|
|
|
- calorie: string;
|
|
|
- amount: string;
|
|
|
- recipes ? : Recipe[];
|
|
|
+ }
|
|
|
+ return (weight * count).toFixed(4);
|
|
|
+}
|
|
|
+
|
|
|
+function getRecipeAmount(recipe, count = 1) {
|
|
|
+ // 如果有 recipe.price 字段
|
|
|
+ let price = 0;
|
|
|
+ if (recipe.amount) {
|
|
|
+ price = parseFloat(recipe.amount) || 0;
|
|
|
+ }
|
|
|
+ return (price * count).toFixed(2);
|
|
|
+}
|
|
|
+
|
|
|
+function getRecipeCalorie(recipe, count = 1) {
|
|
|
+ // 如果有 recipe.calorie 字段
|
|
|
+ let calorie = 0;
|
|
|
+ if (recipe.calorie) {
|
|
|
+ calorie = parseFloat(recipe.calorie) || 0;
|
|
|
+ }
|
|
|
+ return (calorie * count).toFixed(2);
|
|
|
+}
|
|
|
+
|
|
|
+function parseRecipeWeight(recipe) {
|
|
|
+ let weight = 0;
|
|
|
+ if (recipe.weight) {
|
|
|
+ weight = parseFloat(recipe.weight.replace(/[^\d.]/g, '') || '0');
|
|
|
+ } else if (recipe.ingredients) {
|
|
|
+ const matches = recipe.ingredients.match(/([\d.]+)g/g);
|
|
|
+ if (matches) {
|
|
|
+ weight = matches.reduce((sum, item) => {
|
|
|
+ const num = parseFloat(item.replace('g', ''));
|
|
|
+ return sum + (isNaN(num) ? 0 : num);
|
|
|
+ }, 0);
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- // 定义食谱模板数据结构
|
|
|
- interface RecipeTemplateItem {
|
|
|
- name: string;
|
|
|
- categoryId: number;
|
|
|
- categoryName: string;
|
|
|
- meals: Array < {
|
|
|
- meal: string;
|
|
|
- dish: string;
|
|
|
- weight: string;
|
|
|
- kcal: string;
|
|
|
- } > ;
|
|
|
+ }
|
|
|
+ return weight;
|
|
|
+}
|
|
|
+
|
|
|
+const handleSubmit = () => {
|
|
|
+ // 合并所有食谱tab下的明细
|
|
|
+ let mealRecipeList: MealRecipeForm[] = [];
|
|
|
+ allTableData.value.forEach((table, tabIdx) => {
|
|
|
+ mealRecipeList = mealRecipeList.concat(convertTableDataToMealRecipeList(table, tabIdx + 1));
|
|
|
+ });
|
|
|
+ const mealPlanForm = {
|
|
|
+ // ...其它表单字段
|
|
|
+ mealTimeRange: mealTimeRange.value,
|
|
|
+ totalPrice: totalPrice.value,
|
|
|
+ mealRecipeList
|
|
|
+ };
|
|
|
+
|
|
|
+ emit('submitHospital', mealPlanForm);
|
|
|
+ emit('goBack'); // 让父组件切换回 hospitalIndex
|
|
|
+ localStorage.removeItem(DRAFT_KEY.value); // 提交成功后清除缓存
|
|
|
+};
|
|
|
+
|
|
|
+function saveDraft() {
|
|
|
+ const draft = {
|
|
|
+ mealTimeRange: mealTimeRange.value,
|
|
|
+ allTableData: allTableData.value,
|
|
|
+ currentRecipe: currentRecipe.value,
|
|
|
+ totalPrice: totalPrice.value
|
|
|
+ // 你还可以加其它需要缓存的字段
|
|
|
+ };
|
|
|
+ localStorage.setItem(DRAFT_KEY.value, JSON.stringify(draft));
|
|
|
+ ElMessage.success('操作成功');
|
|
|
+}
|
|
|
+
|
|
|
+function loadDraft() {
|
|
|
+ const draftStr = localStorage.getItem(DRAFT_KEY.value);
|
|
|
+ if (draftStr) {
|
|
|
+ try {
|
|
|
+ const draft = JSON.parse(draftStr);
|
|
|
+ if (draft.mealTimeRange) mealTimeRange.value = draft.mealTimeRange;
|
|
|
+ if (draft.allTableData) allTableData.value = draft.allTableData;
|
|
|
+ if (draft.totalPrice) totalPrice.value = draft.totalPrice;
|
|
|
+ if (draft.currentRecipe !== undefined) currentRecipe.value = draft.currentRecipe;
|
|
|
+ } catch (e) {
|
|
|
+ // 解析失败可忽略
|
|
|
}
|
|
|
-
|
|
|
- const mealTimeRange = ref([new Date(), new Date()]);
|
|
|
- const activeTab = ref('nutrition');
|
|
|
- const nutritionTarget = ref(897.3);
|
|
|
- const actualCalories = ref(0.0);
|
|
|
- const proteinRatio = ref(0);
|
|
|
- const fatRatio = ref(0);
|
|
|
- const carbsRatio = ref(0);
|
|
|
-
|
|
|
- const nutritionData = ref([
|
|
|
- { name: '蛋白质', weight: '', ratio: 0, reference: '10% - 15%' },
|
|
|
- { name: '脂肪', weight: '', ratio: 0, reference: '20% - 30%' },
|
|
|
- { name: '碳水化合物', weight: '', ratio: 0, reference: '50% - 65%' }
|
|
|
- ]);
|
|
|
-
|
|
|
- const initialTableData: TableRow[] = [
|
|
|
- { meal: '早餐', time: '08:00', foodId: '', menuName: '', categoryId: '', categoryName: '', count: 1, weight: '', calorie: '', amount: '', recipes: [] },
|
|
|
- { meal: '早加', time: '08:30', foodId: '', menuName: '', categoryId: '', categoryName: '', count: 1, weight: '', calorie: '', amount: '', recipes: [] },
|
|
|
- { meal: '中餐', time: '12:00', foodId: '', menuName: '', categoryId: '', categoryName: '', count: 1, weight: '', calorie: '', amount: '', recipes: [] },
|
|
|
- { meal: '中加', time: '12:30', foodId: '', menuName: '', categoryId: '', categoryName: '', count: 1, weight: '', calorie: '', amount: '', recipes: [] },
|
|
|
- { meal: '晚餐', time: '18:00', foodId: '', menuName: '', categoryId: '', categoryName: '', count: 1, weight: '', calorie: '', amount: '', recipes: [] },
|
|
|
- { meal: '晚加', time: '18:30', foodId: '', menuName: '', categoryId: '', categoryName: '', count: 1, weight: '', calorie: '', amount: '', recipes: [] }
|
|
|
- ];
|
|
|
-
|
|
|
-
|
|
|
- const allTableData = ref < TableRow[][] > ([JSON.parse(JSON.stringify(initialTableData))]);
|
|
|
- const currentRecipe = ref(0);
|
|
|
- const tableData = computed({
|
|
|
- get: () => allTableData.value[currentRecipe.value],
|
|
|
- set: (val) => { allTableData.value[currentRecipe.value] = val; }
|
|
|
- });
|
|
|
-
|
|
|
- const recipes = ref([0]); // 食谱列表
|
|
|
-
|
|
|
- // 添加食谱
|
|
|
- // 新增食谱,最多7个
|
|
|
- const addRecipe = () => {
|
|
|
- if (allTableData.value.length >= 7) {
|
|
|
- ElMessage.warning('最多只能添加7个食谱');
|
|
|
- return;
|
|
|
- }
|
|
|
- allTableData.value.push(JSON.parse(JSON.stringify(initialTableData)));
|
|
|
- currentRecipe.value = allTableData.value.length - 1;
|
|
|
- };
|
|
|
-
|
|
|
- function convertTableDataToMealRecipeList(tableData: TableRow[], tabNo: number): MealRecipeForm[] {
|
|
|
- const result: MealRecipeForm[] = [];
|
|
|
- tableData.forEach((row) => {
|
|
|
- if (row.recipes && row.recipes.length > 0) {
|
|
|
- row.recipes.forEach((recipe) => {
|
|
|
- // 计算单个食谱的重量并乘以份数
|
|
|
- const singleWeight = parseRecipeWeight(recipe);
|
|
|
- result.push({
|
|
|
- recipeNo: tabNo,
|
|
|
- mealTime: row.meal,
|
|
|
- eatTime: row.time,
|
|
|
- foodId: recipe.id,
|
|
|
- foodName: recipe.name,
|
|
|
- foodCategoryId: recipe.categoryId,
|
|
|
- foodWeight: singleWeight * row.count, // 这里乘以份数
|
|
|
- calorie: recipe.calorie ? Number(recipe.calorie) : undefined,
|
|
|
- amount: row.count,
|
|
|
- foodCategoryName: recipe.categoryName,
|
|
|
- price: row.amount ? Number(row.amount) : undefined,
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
- });
|
|
|
- return result;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 切换食谱
|
|
|
+const switchRecipe = (index: number) => {
|
|
|
+ currentRecipe.value = index;
|
|
|
+};
|
|
|
+
|
|
|
+// 删除食谱
|
|
|
+const deleteRecipe = (index: number) => {
|
|
|
+ if (allTableData.value.length === 1) return;
|
|
|
+ allTableData.value.splice(index, 1);
|
|
|
+ if (currentRecipe.value >= allTableData.value.length) {
|
|
|
+ currentRecipe.value = allTableData.value.length - 1;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 删除所有食谱
|
|
|
+const deleteAllRecipes = () => {
|
|
|
+ allTableData.value = [JSON.parse(JSON.stringify(initialTableData))];
|
|
|
+ currentRecipe.value = 0;
|
|
|
+};
|
|
|
+
|
|
|
+// 返回上一页按钮
|
|
|
+const goBack = () => {
|
|
|
+ emit('goBack');
|
|
|
+};
|
|
|
+
|
|
|
+// 食谱选择对话框
|
|
|
+
|
|
|
+const searchKeyword = ref('');
|
|
|
+const currentCategory = ref('');
|
|
|
+const currentPage = ref(1);
|
|
|
+const pageSize = ref(10);
|
|
|
+const total = ref(1869);
|
|
|
+const selectedRecipes = ref<Recipe[]>([]);
|
|
|
+
|
|
|
+const currentRow = ref<TableRow | null>(null);
|
|
|
+const currentIndex = ref<number>(-1);
|
|
|
+const dialogRef = ref<InstanceType<typeof CommonDialog> | null>(null);
|
|
|
+
|
|
|
+// 处理食谱确认
|
|
|
+const handleRecipeConfirm = (selectedRecipes: Recipe[]) => {
|
|
|
+ if (currentRow.value && selectedRecipes.length > 0) {
|
|
|
+ if (!currentRow.value.recipes) {
|
|
|
+ currentRow.value.recipes = [];
|
|
|
}
|
|
|
|
|
|
- function getRecipeWeight(recipe, count = 1) {
|
|
|
- let weight = 0;
|
|
|
- if (recipe.weight) {
|
|
|
- weight = parseFloat(recipe.weight.replace(/[^\d.]/g, '') || '0');
|
|
|
- } else if (recipe.ingredients) {
|
|
|
- const matches = recipe.ingredients.match(/([\d.]+)g/g);
|
|
|
- if (matches) {
|
|
|
- weight = matches.reduce((sum, item) => {
|
|
|
- const num = parseFloat(item.replace('g', ''));
|
|
|
- return sum + (isNaN(num) ? 0 : num);
|
|
|
- }, 0);
|
|
|
- }
|
|
|
- }
|
|
|
- return (weight * count).toFixed(4);
|
|
|
+ if (currentIndex.value >= 0) {
|
|
|
+ // 更新已有食谱
|
|
|
+ currentRow.value.recipes[currentIndex.value] = selectedRecipes[0];
|
|
|
+ } else {
|
|
|
+ // 添加新食谱
|
|
|
+ currentRow.value.recipes.push(...selectedRecipes);
|
|
|
}
|
|
|
|
|
|
- function parseRecipeWeight(recipe) {
|
|
|
- let weight = 0;
|
|
|
- if (recipe.weight) {
|
|
|
- weight = parseFloat(recipe.weight.replace(/[^\d.]/g, '') || '0');
|
|
|
- } else if (recipe.ingredients) {
|
|
|
- const matches = recipe.ingredients.match(/([\d.]+)g/g);
|
|
|
- if (matches) {
|
|
|
- weight = matches.reduce((sum, item) => {
|
|
|
- const num = parseFloat(item.replace('g', ''));
|
|
|
- return sum + (isNaN(num) ? 0 : num);
|
|
|
- }, 0);
|
|
|
- }
|
|
|
+ updateRowData(currentRow.value);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 处理食谱取消
|
|
|
+const handleRecipeCancel = () => {};
|
|
|
+
|
|
|
+// 移除食谱
|
|
|
+const removeRecipe = (row: TableRow, index: number) => {
|
|
|
+ row.recipes?.splice(index, 1);
|
|
|
+ updateRowData(row);
|
|
|
+};
|
|
|
+
|
|
|
+// 处理食谱模板确认选择
|
|
|
+const handleRecipeTemplateConfirm = (selectedRecipes: RecipeTemplateItem[]) => {
|
|
|
+ // 遍历选中的食谱
|
|
|
+ selectedRecipes.forEach((recipe) => {
|
|
|
+ // 遍历食谱中的每一餐
|
|
|
+ recipe.meals.forEach((mealData) => {
|
|
|
+ // 找到对应的表格行
|
|
|
+ const targetRow = tableData.value.find((row) => row.meal === mealData.meal);
|
|
|
+ if (targetRow) {
|
|
|
+ // 如果没有recipes数组,创建一个
|
|
|
+ if (!targetRow.recipes) {
|
|
|
+ targetRow.recipes = [];
|
|
|
}
|
|
|
- return weight;
|
|
|
- }
|
|
|
|
|
|
- const handleSubmit = () => {
|
|
|
- // 合并所有食谱tab下的明细
|
|
|
- let mealRecipeList: MealRecipeForm[] = [];
|
|
|
- allTableData.value.forEach((table, tabIdx) => {
|
|
|
- mealRecipeList = mealRecipeList.concat(convertTableDataToMealRecipeList(table, tabIdx + 1));
|
|
|
+ // 添加新的食谱项
|
|
|
+ targetRow.recipes.push({
|
|
|
+ id: Date.now(), // 临时ID
|
|
|
+ name: mealData.dish,
|
|
|
+ categoryId: mealData.categoryId,
|
|
|
+ categoryName: mealData.categoryName,
|
|
|
+ ingredients: `${mealData.weight}; ${mealData.kcal}`,
|
|
|
+ weight: mealData.weight,
|
|
|
+ calorie: mealData.kcal
|
|
|
});
|
|
|
- const mealPlanForm = {
|
|
|
- // ...其它表单字段
|
|
|
- mealTimeRange: mealTimeRange.value,
|
|
|
- mealRecipeList
|
|
|
- };
|
|
|
-
|
|
|
- emit('submitHospital', mealPlanForm);
|
|
|
- emit('goBack'); // 让父组件切换回 hospitalIndex
|
|
|
- localStorage.removeItem(DRAFT_KEY.value); // 提交成功后清除缓存
|
|
|
-
|
|
|
- };
|
|
|
-
|
|
|
- function saveDraft() {
|
|
|
- const draft = {
|
|
|
- mealTimeRange: mealTimeRange.value,
|
|
|
- allTableData: allTableData.value,
|
|
|
- currentRecipe: currentRecipe.value,
|
|
|
- // 你还可以加其它需要缓存的字段
|
|
|
- };
|
|
|
- localStorage.setItem(DRAFT_KEY.value, JSON.stringify(draft));
|
|
|
- ElMessage.success('操作成功');
|
|
|
- }
|
|
|
|
|
|
- function loadDraft() {
|
|
|
- const draftStr = localStorage.getItem(DRAFT_KEY.value);
|
|
|
- if (draftStr) {
|
|
|
- try {
|
|
|
- const draft = JSON.parse(draftStr);
|
|
|
- if (draft.mealTimeRange) mealTimeRange.value = draft.mealTimeRange;
|
|
|
- if (draft.allTableData) allTableData.value = draft.allTableData;
|
|
|
- if (draft.currentRecipe !== undefined) currentRecipe.value = draft.currentRecipe;
|
|
|
- } catch (e) {
|
|
|
- // 解析失败可忽略
|
|
|
- }
|
|
|
+ // 更新行数据
|
|
|
+ updateRowData(targetRow);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+function sumIngredientsWeight(ingredients) {
|
|
|
+ if (!ingredients) return 0;
|
|
|
+ // 匹配所有形如 50.0000g 的数字
|
|
|
+ const matches = ingredients.match(/([\d.]+)g/g);
|
|
|
+ if (!matches) return 0;
|
|
|
+ return matches.reduce((sum, item) => {
|
|
|
+ const num = parseFloat(item.replace('g', ''));
|
|
|
+ return sum + (isNaN(num) ? 0 : num);
|
|
|
+ }, 0);
|
|
|
+}
|
|
|
+
|
|
|
+// 更新行数据的逻辑优化
|
|
|
+const updateRowData = (row: TableRow) => {
|
|
|
+ if (row.recipes?.length) {
|
|
|
+ // 更新菜品名称
|
|
|
+ row.menuName = row.recipes.map((recipe) => recipe.name).join('、');
|
|
|
+ // row.foodId = row.recipes.map((recipe) => recipe.id).join('、');
|
|
|
+
|
|
|
+ // 计算总重量和热量
|
|
|
+ let totalCalorie = 0;
|
|
|
+ let totalWeight = 0;
|
|
|
+ let totalAmount = 0;
|
|
|
+ row.recipes.forEach((recipe) => {
|
|
|
+ // 如果有 recipe.weight 字段优先用,否则解析 ingredients
|
|
|
+ const weight = parseFloat(recipe.weight?.replace(/[^\d.]/g, '') || '0');
|
|
|
+ totalWeight += weight;
|
|
|
+
|
|
|
+ const calorie = parseFloat(recipe.calorie || '0');
|
|
|
+ totalCalorie += calorie;
|
|
|
+
|
|
|
+ const amount = parseFloat(recipe.amount || '0');
|
|
|
+ totalAmount += amount;
|
|
|
+ });
|
|
|
+ totalWeight = totalWeight * row.count;
|
|
|
+ row.weight = totalWeight.toFixed(4);
|
|
|
+
|
|
|
+ totalCalorie = totalCalorie * row.count;
|
|
|
+ row.calorie = totalCalorie.toFixed(4);
|
|
|
+
|
|
|
+ totalAmount = totalAmount * row.count;
|
|
|
+ row.amount = totalAmount.toFixed(2);
|
|
|
+
|
|
|
+ // 金额也乘以份数
|
|
|
+ row.amount = (0.01 * row.recipes.length * row.count).toFixed(2);
|
|
|
+ } else {
|
|
|
+ // 清空数据
|
|
|
+ row.foodId = '';
|
|
|
+ row.menuName = '';
|
|
|
+ row.categoryId = '';
|
|
|
+ row.weight = '';
|
|
|
+ row.calorie = '';
|
|
|
+ row.amount = '0.00';
|
|
|
+ row.recipes = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新总价格
|
|
|
+ calculateTotalPrice();
|
|
|
+};
|
|
|
+
|
|
|
+// 监听患者切换自动加载草稿
|
|
|
+watch(
|
|
|
+ () => props.patientInfo.id,
|
|
|
+ () => {
|
|
|
+ loadDraft();
|
|
|
+ },
|
|
|
+ {immediate: true}
|
|
|
+);
|
|
|
+
|
|
|
+// 监听食谱数据变化,重新计算总价格
|
|
|
+watch(
|
|
|
+ allTableData,
|
|
|
+ () => {
|
|
|
+ calculateTotalPrice();
|
|
|
+ },
|
|
|
+ {deep: true}
|
|
|
+);
|
|
|
+
|
|
|
+// 监听当前食谱切换,重新计算总价格
|
|
|
+watch(currentRecipe, () => {
|
|
|
+ calculateTotalPrice();
|
|
|
+});
|
|
|
+
|
|
|
+// 分页相关
|
|
|
+const handleSizeChange = (val: number) => {
|
|
|
+ pageSize.value = val;
|
|
|
+ // TODO: 重新加载数据
|
|
|
+};
|
|
|
+
|
|
|
+const handleCurrentChange = (val: number) => {
|
|
|
+ currentPage.value = val;
|
|
|
+ // TODO: 重新加载数据
|
|
|
+};
|
|
|
+
|
|
|
+// 处理 select 显示状态变化
|
|
|
+const handleSelectVisibleChange = (visible: boolean, row: any) => {
|
|
|
+ if (visible) {
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+// 打开食谱选择弹窗
|
|
|
+const openRecipeDialog = (row: TableRow, index?: number) => {
|
|
|
+ currentRow.value = row;
|
|
|
+ currentIndex.value = index ?? -1;
|
|
|
+
|
|
|
+ // 传递当前行的所有食谱给弹窗
|
|
|
+ if (row.recipes?.length) {
|
|
|
+ dialogRef.value?.openDialog('recipe', row.recipes);
|
|
|
+ } else {
|
|
|
+ dialogRef.value?.openDialog('recipe');
|
|
|
+ }
|
|
|
+};
|
|
|
+const recipeTemplateRef = ref<InstanceType<typeof RecipeTemplate> | null>(null);
|
|
|
+// 打开食谱模板弹窗
|
|
|
+const openRecipeTemplate = () => {
|
|
|
+ recipeTemplateRef.value?.openDialog();
|
|
|
+};
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => props.rowData,
|
|
|
+ (newVal) => {
|
|
|
+ if (newVal?.mealRecipeList) {
|
|
|
+ // 1. 清空现有数据
|
|
|
+ allTableData.value = [];
|
|
|
+
|
|
|
+ // 2. 按 recipeNo 分组
|
|
|
+ const recipesByNo: Record<number, MealRecipeForm[]> = {};
|
|
|
+ newVal.mealRecipeList.forEach((recipe) => {
|
|
|
+ if (!recipesByNo[recipe.recipeNo]) {
|
|
|
+ recipesByNo[recipe.recipeNo] = [];
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
+ recipesByNo[recipe.recipeNo].push(recipe);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 3. 为每个 recipeNo 创建对应的食谱 tab
|
|
|
+ Object.keys(recipesByNo).forEach((no) => {
|
|
|
+ const recipeNo = parseInt(no, 10);
|
|
|
+ const recipes = recipesByNo[recipeNo];
|
|
|
+
|
|
|
+ // 初始化一个新的食谱 tab
|
|
|
+ const newTableData = JSON.parse(JSON.stringify(initialTableData));
|
|
|
+
|
|
|
+ // 填充数据
|
|
|
+ recipes.forEach((recipe) => {
|
|
|
+ const targetRow = newTableData.find((row) => row.meal === recipe.mealTime);
|
|
|
+ if (targetRow) {
|
|
|
+ if (!targetRow.recipes) {
|
|
|
+ targetRow.recipes = [];
|
|
|
+ }
|
|
|
|
|
|
+ targetRow.recipes.push({
|
|
|
+ id: recipe.foodId,
|
|
|
+ name: recipe.foodName,
|
|
|
+ categoryId: recipe.foodCategoryId,
|
|
|
+ categoryName: recipe.foodCategoryName,
|
|
|
+ weight: `${recipe.foodWeight}g`,
|
|
|
+ calorie: recipe.calorie?.toString(),
|
|
|
+ amount: recipe.price?.toString()
|
|
|
+ });
|
|
|
|
|
|
- // 切换食谱
|
|
|
- const switchRecipe = (index: number) => {
|
|
|
- currentRecipe.value = index;
|
|
|
- };
|
|
|
+ // 更新用餐时间
|
|
|
+ if (recipe.eatTime) {
|
|
|
+ const time = new Date(recipe.eatTime);
|
|
|
+ const hours = time.getHours().toString().padStart(2, '0');
|
|
|
+ const minutes = time.getMinutes().toString().padStart(2, '0');
|
|
|
+ targetRow.time = `${hours}:${minutes}`;
|
|
|
+ }
|
|
|
|
|
|
- // 删除食谱
|
|
|
- const deleteRecipe = (index: number) => {
|
|
|
- if (allTableData.value.length === 1) return;
|
|
|
- allTableData.value.splice(index, 1);
|
|
|
- if (currentRecipe.value >= allTableData.value.length) {
|
|
|
- currentRecipe.value = allTableData.value.length - 1;
|
|
|
- }
|
|
|
- };
|
|
|
+ // 更新份数
|
|
|
+ targetRow.count = recipe.amount || 1;
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
- // 删除所有食谱
|
|
|
- const deleteAllRecipes = () => {
|
|
|
+ // 添加到 allTableData
|
|
|
+ allTableData.value.push(newTableData);
|
|
|
+ });
|
|
|
|
|
|
+ // 4. 如果没有数据,至少保留一个空 tab
|
|
|
+ if (allTableData.value.length === 0) {
|
|
|
allTableData.value = [JSON.parse(JSON.stringify(initialTableData))];
|
|
|
- currentRecipe.value = 0;
|
|
|
- };
|
|
|
-
|
|
|
- // 初始化图表
|
|
|
- const initChart = () => {
|
|
|
- const chartDom = document.getElementById('nutritionChart');
|
|
|
- if (!chartDom) return;
|
|
|
-
|
|
|
- const myChart = echarts.init(chartDom);
|
|
|
- const option = {
|
|
|
- tooltip: {
|
|
|
- trigger: 'item'
|
|
|
- },
|
|
|
- legend: {
|
|
|
- show: false
|
|
|
- },
|
|
|
- color: ['#00D1D1', '#FFC53D', '#2F54EB'],
|
|
|
- series: [{
|
|
|
- name: '营养元素',
|
|
|
- type: 'pie',
|
|
|
- radius: ['60%', '80%'],
|
|
|
- avoidLabelOverlap: false,
|
|
|
- itemStyle: {
|
|
|
- borderRadius: 0,
|
|
|
- borderColor: '#fff',
|
|
|
- borderWidth: 2
|
|
|
- },
|
|
|
- label: {
|
|
|
- show: false
|
|
|
- },
|
|
|
- emphasis: {
|
|
|
- disabled: true
|
|
|
- },
|
|
|
- labelLine: {
|
|
|
- show: false
|
|
|
- },
|
|
|
- data: [
|
|
|
- { value: proteinRatio.value || 1, name: '蛋白质' },
|
|
|
- { value: fatRatio.value || 1, name: '脂肪' },
|
|
|
- { value: carbsRatio.value || 1, name: '碳水化合物' }
|
|
|
- ]
|
|
|
- }]
|
|
|
- };
|
|
|
-
|
|
|
- option && myChart.setOption(option);
|
|
|
- };
|
|
|
-
|
|
|
- // 返回上一页按钮
|
|
|
- const goBack = () => {
|
|
|
- emit('goBack');
|
|
|
- };
|
|
|
-
|
|
|
- // 食谱选择对话框
|
|
|
-
|
|
|
- const searchKeyword = ref('');
|
|
|
- const currentCategory = ref('');
|
|
|
- const currentPage = ref(1);
|
|
|
- const pageSize = ref(10);
|
|
|
- const total = ref(1869);
|
|
|
- const selectedRecipes = ref < Recipe[] > ([]);
|
|
|
-
|
|
|
- const currentRow = ref < TableRow | null > (null);
|
|
|
- const currentIndex = ref < number > (-1);
|
|
|
- const dialogRef = ref < InstanceType < typeof CommonDialog > | null > (null);
|
|
|
-
|
|
|
- // 处理食谱确认
|
|
|
- const handleRecipeConfirm = (selectedRecipes: Recipe[]) => {
|
|
|
- if (currentRow.value && selectedRecipes.length > 0) {
|
|
|
- if (!currentRow.value.recipes) {
|
|
|
- currentRow.value.recipes = [];
|
|
|
- }
|
|
|
+ }
|
|
|
|
|
|
- if (currentIndex.value >= 0) {
|
|
|
- // 更新已有食谱
|
|
|
- currentRow.value.recipes[currentIndex.value] = selectedRecipes[0];
|
|
|
- } else {
|
|
|
- // 添加新食谱
|
|
|
- currentRow.value.recipes.push(...selectedRecipes);
|
|
|
- }
|
|
|
-
|
|
|
- updateRowData(currentRow.value);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // 处理食谱取消
|
|
|
- const handleRecipeCancel = () => {
|
|
|
- };
|
|
|
-
|
|
|
- // 移除食谱
|
|
|
- const removeRecipe = (row: TableRow, index: number) => {
|
|
|
- row.recipes ?.splice(index, 1);
|
|
|
- updateRowData(row);
|
|
|
- };
|
|
|
-
|
|
|
- // 处理食谱模板确认选择
|
|
|
- const handleRecipeTemplateConfirm = (selectedRecipes: RecipeTemplateItem[]) => {
|
|
|
- // 遍历选中的食谱
|
|
|
- selectedRecipes.forEach((recipe) => {
|
|
|
- // 遍历食谱中的每一餐
|
|
|
- recipe.meals.forEach((mealData) => {
|
|
|
- // 找到对应的表格行
|
|
|
- const targetRow = tableData.value.find((row) => row.meal === mealData.meal);
|
|
|
- if (targetRow) {
|
|
|
- // 如果没有recipes数组,创建一个
|
|
|
- if (!targetRow.recipes) {
|
|
|
- targetRow.recipes = [];
|
|
|
- }
|
|
|
-
|
|
|
- // 添加新的食谱项
|
|
|
- targetRow.recipes.push({
|
|
|
- id: Date.now(), // 临时ID
|
|
|
- name: mealData.dish,
|
|
|
- categoryId: mealData.categoryId,
|
|
|
- categoryName: mealData.categoryName,
|
|
|
- ingredients: `${mealData.weight}; ${mealData.kcal}`,
|
|
|
- weight: mealData.weight,
|
|
|
- calorie: mealData.kcal
|
|
|
- });
|
|
|
-
|
|
|
- // 更新行数据
|
|
|
- updateRowData(targetRow);
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
- };
|
|
|
-
|
|
|
- function sumIngredientsWeight(ingredients) {
|
|
|
- if (!ingredients) return 0;
|
|
|
- // 匹配所有形如 50.0000g 的数字
|
|
|
- const matches = ingredients.match(/([\d.]+)g/g);
|
|
|
- if (!matches) return 0;
|
|
|
- return matches.reduce((sum, item) => {
|
|
|
- const num = parseFloat(item.replace('g', ''));
|
|
|
- return sum + (isNaN(num) ? 0 : num);
|
|
|
- }, 0);
|
|
|
+ // 5. 数据填充完成后,设置总价格(使用 nextTick 确保在 DOM 更新后执行)
|
|
|
+ nextTick(() => {
|
|
|
+ totalPrice.value = Number(newVal.totalPrice) || 0;
|
|
|
+ });
|
|
|
}
|
|
|
-
|
|
|
- // 更新行数据的逻辑优化
|
|
|
- const updateRowData = (row: TableRow) => {
|
|
|
-
|
|
|
- if (row.recipes ?.length) {
|
|
|
- // 更新菜品名称
|
|
|
- row.menuName = row.recipes.map((recipe) => recipe.name).join('、');
|
|
|
- // row.foodId = row.recipes.map((recipe) => recipe.id).join('、');
|
|
|
-
|
|
|
- // 计算总重量和热量
|
|
|
- let totalCalorie = 0;
|
|
|
-
|
|
|
- let totalWeight = 0;
|
|
|
- row.recipes.forEach((recipe) => {
|
|
|
- // 如果有 recipe.weight 字段优先用,否则解析 ingredients
|
|
|
- let weight = 0;
|
|
|
- if (recipe.weight) {
|
|
|
- weight = parseFloat(recipe.weight.replace(/[^\d.]/g, '') || '0');
|
|
|
- } else if (recipe.ingredients) {
|
|
|
- weight = sumIngredientsWeight(recipe.ingredients);
|
|
|
- }
|
|
|
- totalWeight += weight;
|
|
|
- });
|
|
|
- totalWeight = totalWeight * row.count;
|
|
|
- row.weight = totalWeight.toFixed(4) + 'g';
|
|
|
-
|
|
|
- row.calorie = totalCalorie.toFixed(4) ;
|
|
|
- // 金额也乘以份数
|
|
|
- row.amount = (0.01 * row.recipes.length * row.count).toFixed(2);
|
|
|
- } else {
|
|
|
- // 清空数据
|
|
|
- row.foodId = '';
|
|
|
- row.menuName = '';
|
|
|
- row.categoryId = '';
|
|
|
- row.weight = '';
|
|
|
- row.calorie = '';
|
|
|
- row.amount = '0.00';
|
|
|
- row.recipes = [];
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // 监听患者切换自动加载草稿
|
|
|
- watch(() => props.patientInfo.id, () => {
|
|
|
- loadDraft();
|
|
|
- }, { immediate: true });
|
|
|
-
|
|
|
- // 分页相关
|
|
|
- const handleSizeChange = (val: number) => {
|
|
|
- pageSize.value = val;
|
|
|
- // TODO: 重新加载数据
|
|
|
- };
|
|
|
-
|
|
|
- const handleCurrentChange = (val: number) => {
|
|
|
- currentPage.value = val;
|
|
|
- // TODO: 重新加载数据
|
|
|
- };
|
|
|
-
|
|
|
- // 处理 select 显示状态变化
|
|
|
- const handleSelectVisibleChange = (visible: boolean, row: any) => {
|
|
|
- if (visible) {}
|
|
|
- };
|
|
|
-
|
|
|
- // 打开食谱选择弹窗
|
|
|
- const openRecipeDialog = (row: TableRow, index ? : number) => {
|
|
|
- currentRow.value = row;
|
|
|
- currentIndex.value = index ?? -1;
|
|
|
-
|
|
|
- // 传递当前行的所有食谱给弹窗
|
|
|
- if (row.recipes ?.length) {
|
|
|
- dialogRef.value ?.openDialog('recipe', row.recipes);
|
|
|
- } else {
|
|
|
- dialogRef.value ?.openDialog('recipe');
|
|
|
- }
|
|
|
- };
|
|
|
- const recipeTemplateRef = ref < InstanceType < typeof RecipeTemplate > | null > (null);
|
|
|
- // 打开食谱模板弹窗
|
|
|
- const openRecipeTemplate = () => {
|
|
|
- recipeTemplateRef.value ?.openDialog();
|
|
|
- };
|
|
|
-
|
|
|
- onMounted(() => {
|
|
|
- initChart();
|
|
|
- loadDraft();
|
|
|
- });
|
|
|
+ },
|
|
|
+ {immediate: true}
|
|
|
+);
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ loadDraft();
|
|
|
+ calculateTotalPrice();
|
|
|
+});
|
|
|
</script>
|
|
|
<style lang="scss" scoped>
|
|
|
- .container {
|
|
|
- position: relative;
|
|
|
-
|
|
|
- min-height: 90vh;
|
|
|
- padding-bottom: 76px; // 底部按钮的高度 + padding
|
|
|
-
|
|
|
- .el-card {
|
|
|
- margin-top: 16px;
|
|
|
- height: 500px;
|
|
|
- width: 100vw;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .add-hospital-wrapper {
|
|
|
- width: 89vw;
|
|
|
- background: #fff;
|
|
|
- border-radius: 12px;
|
|
|
- padding: 20px 24px 12px 24px;
|
|
|
- box-shadow: 0 2px 8px rgba(64, 158, 255, 0.04);
|
|
|
- }
|
|
|
-
|
|
|
- .header-bar {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 16px;
|
|
|
-
|
|
|
- .back-btn {
|
|
|
- margin-right: 16px;
|
|
|
- }
|
|
|
-
|
|
|
- .title {
|
|
|
- font-size: 20px;
|
|
|
- font-weight: 600;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .form-bar {
|
|
|
- width: 100vm;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: 12px;
|
|
|
-
|
|
|
- .ml-2 {
|
|
|
- margin-left: 12px;
|
|
|
- }
|
|
|
+.container {
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ min-height: 90vh;
|
|
|
+ padding-bottom: 76px; // 底部按钮的高度 + padding
|
|
|
+
|
|
|
+ .el-card {
|
|
|
+ margin-top: 16px;
|
|
|
+ height: 500px;
|
|
|
+ width: 100vw;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.add-hospital-wrapper {
|
|
|
+ width: 89vw;
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 12px;
|
|
|
+ padding: 20px 24px 12px 24px;
|
|
|
+ box-shadow: 0 2px 8px rgba(64, 158, 255, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.header-bar {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 16px;
|
|
|
+
|
|
|
+ .back-btn {
|
|
|
+ margin-right: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .title {
|
|
|
+ font-size: 20px;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.form-bar {
|
|
|
+ width: 100vm;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 12px;
|
|
|
+
|
|
|
+ .ml-2 {
|
|
|
+ margin-left: 12px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.tab-bar {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ .tab-btn {
|
|
|
+ position: relative;
|
|
|
+ padding-right: 32px;
|
|
|
+
|
|
|
+ &.active {
|
|
|
+ background: #4cd964;
|
|
|
+ color: #fff;
|
|
|
+ border: none;
|
|
|
}
|
|
|
|
|
|
- .tab-bar {
|
|
|
- margin-bottom: 8px;
|
|
|
- display: flex;
|
|
|
- flex-wrap: wrap;
|
|
|
- gap: 8px;
|
|
|
-
|
|
|
- .tab-btn {
|
|
|
- position: relative;
|
|
|
- padding-right: 32px;
|
|
|
+ &:not(.active) {
|
|
|
+ background: #f5f7fa;
|
|
|
+ color: #909399;
|
|
|
+ border-color: #dcdfe6;
|
|
|
|
|
|
- &.active {
|
|
|
- background: #4cd964;
|
|
|
- color: #fff;
|
|
|
- border: none;
|
|
|
- }
|
|
|
-
|
|
|
- &:not(.active) {
|
|
|
- background: #f5f7fa;
|
|
|
- color: #909399;
|
|
|
- border-color: #dcdfe6;
|
|
|
-
|
|
|
- .close-icon {
|
|
|
- color: #909399;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .close-icon {
|
|
|
- position: absolute;
|
|
|
- right: 8px;
|
|
|
- top: 50%;
|
|
|
- transform: translateY(-50%);
|
|
|
- cursor: pointer;
|
|
|
- font-size: 14px;
|
|
|
- border-radius: 50%;
|
|
|
- padding: 2px;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- background-color: rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ .close-icon {
|
|
|
+ color: #909399;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- .info-alert {
|
|
|
- margin-bottom: 8px;
|
|
|
- font-size: 14px;
|
|
|
+ .close-icon {
|
|
|
+ position: absolute;
|
|
|
+ right: 8px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 14px;
|
|
|
+ border-radius: 50%;
|
|
|
+ padding: 2px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background-color: rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- .diet-table {
|
|
|
- margin-bottom: 12px;
|
|
|
- }
|
|
|
-
|
|
|
- .footer-bar {
|
|
|
- display: flex;
|
|
|
- justify-content: flex-end;
|
|
|
- align-items: center;
|
|
|
- font-size: 16px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.info-alert {
|
|
|
+ margin-bottom: 8px;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.diet-table {
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.footer-bar {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ align-items: center;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ margin: 8px 0;
|
|
|
+
|
|
|
+ .amount {
|
|
|
+ color: #ff3a00;
|
|
|
+ margin-left: 8px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.nutrition-analysis {
|
|
|
+ padding: 12px;
|
|
|
+
|
|
|
+ .nutrition-header {
|
|
|
+ display: flex;
|
|
|
+ gap: 24px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ font-size: 14px;
|
|
|
+
|
|
|
+ .nutrition-target,
|
|
|
+ .nutrition-actual {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+
|
|
|
+ .value {
|
|
|
+ color: #409eff;
|
|
|
font-weight: 600;
|
|
|
- margin: 8px 0;
|
|
|
-
|
|
|
- .amount {
|
|
|
- color: #ff3a00;
|
|
|
- margin-left: 8px;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .nutrition-analysis {
|
|
|
- padding: 12px;
|
|
|
-
|
|
|
- .nutrition-header {
|
|
|
- display: flex;
|
|
|
- gap: 24px;
|
|
|
- margin-bottom: 12px;
|
|
|
- font-size: 14px;
|
|
|
-
|
|
|
- .nutrition-target,
|
|
|
- .nutrition-actual {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 8px;
|
|
|
-
|
|
|
- .value {
|
|
|
- color: #409eff;
|
|
|
- font-weight: 600;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- .chart-container {
|
|
|
- margin-top: 16px;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- text-align: center;
|
|
|
-
|
|
|
- .chart-title {
|
|
|
- font-size: 16px;
|
|
|
- font-weight: 600;
|
|
|
- margin-bottom: 16px;
|
|
|
- color: #333;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.chart-container {
|
|
|
+ margin-top: 16px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ .chart-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-content {
|
|
|
+ height: 200px;
|
|
|
+ width: 200px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .chart-legend {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ gap: 24px;
|
|
|
+ margin-top: 16px;
|
|
|
+
|
|
|
+ .legend-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ color: #666;
|
|
|
+ font-size: 14px;
|
|
|
+
|
|
|
+ .color-block {
|
|
|
+ width: 12px;
|
|
|
+ height: 12px;
|
|
|
+ border-radius: 2px;
|
|
|
+
|
|
|
+ &.protein {
|
|
|
+ background-color: #00d1d1;
|
|
|
}
|
|
|
|
|
|
- .chart-content {
|
|
|
- height: 200px;
|
|
|
- width: 200px;
|
|
|
+ &.fat {
|
|
|
+ background-color: #ffc53d;
|
|
|
}
|
|
|
|
|
|
- .chart-legend {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- gap: 24px;
|
|
|
- margin-top: 16px;
|
|
|
-
|
|
|
- .legend-item {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 8px;
|
|
|
- color: #666;
|
|
|
- font-size: 14px;
|
|
|
-
|
|
|
- .color-block {
|
|
|
- width: 12px;
|
|
|
- height: 12px;
|
|
|
- border-radius: 2px;
|
|
|
-
|
|
|
- &.protein {
|
|
|
- background-color: #00d1d1;
|
|
|
- }
|
|
|
-
|
|
|
- &.fat {
|
|
|
- background-color: #ffc53d;
|
|
|
- }
|
|
|
-
|
|
|
- &.carbs {
|
|
|
- background-color: #2f54eb;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ &.carbs {
|
|
|
+ background-color: #2f54eb;
|
|
|
}
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- .mt-4 {
|
|
|
- margin-top: 12px;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.mt-4 {
|
|
|
+ margin-top: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.bottom-btn-card {
|
|
|
+ position: fixed;
|
|
|
+ left: 228px; // 左侧菜单的宽度
|
|
|
+ right: 0;
|
|
|
+ bottom: 50px;
|
|
|
+ padding: 8px 16px;
|
|
|
+ background: #fff;
|
|
|
+ box-shadow: 0 -2px 8px rgba(64, 158, 255, 0.08);
|
|
|
+ z-index: 2000;
|
|
|
+
|
|
|
+ .btn-wrapper {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+
|
|
|
+ .el-button {
|
|
|
+ margin-top: 30px;
|
|
|
+ min-width: 120px;
|
|
|
+ height: 44px;
|
|
|
+ padding: 0 24px;
|
|
|
}
|
|
|
-
|
|
|
- .bottom-btn-card {
|
|
|
- position: fixed;
|
|
|
- left: 228px; // 左侧菜单的宽度
|
|
|
- right: 0;
|
|
|
- bottom: 50px;
|
|
|
- padding: 8px 16px;
|
|
|
- background: #fff;
|
|
|
- box-shadow: 0 -2px 8px rgba(64, 158, 255, 0.08);
|
|
|
- z-index: 2000;
|
|
|
-
|
|
|
- .btn-wrapper {
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
-
|
|
|
- .el-button {
|
|
|
- margin-top: 30px;
|
|
|
- min-width: 120px;
|
|
|
- height: 44px;
|
|
|
- padding: 0 24px;
|
|
|
- }
|
|
|
- }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-main) {
|
|
|
+ overflow-x: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.hidden-popper) {
|
|
|
+ display: none !important;
|
|
|
+}
|
|
|
+
|
|
|
+.recipe-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 32px;
|
|
|
+ line-height: 32px;
|
|
|
+ padding: 0 12px;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #606266;
|
|
|
+ background-color: #fff;
|
|
|
+ position: relative;
|
|
|
+ user-select: none; // 防止文本被选中
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ right: 8px;
|
|
|
+ top: 14px;
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border: 6px solid transparent;
|
|
|
+ border-top-color: #c0c4cc;
|
|
|
+ pointer-events: none; // 防止箭头影响点击
|
|
|
+ }
|
|
|
+
|
|
|
+ &.add-more {
|
|
|
+ margin-top: 8px;
|
|
|
+ color: #909399;
|
|
|
+ border-style: dashed;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.recipe-list {
|
|
|
+ .recipe-input {
|
|
|
+ width: 100%;
|
|
|
+ height: 32px;
|
|
|
+ line-height: 32px;
|
|
|
+ padding: 0 12px;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+ cursor: pointer;
|
|
|
+ color: #606266;
|
|
|
+ background-color: #fff;
|
|
|
+ position: relative;
|
|
|
+ user-select: none;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #409eff;
|
|
|
}
|
|
|
|
|
|
- :deep(.el-main) {
|
|
|
- overflow-x: hidden;
|
|
|
+ &::after {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ right: 8px;
|
|
|
+ top: 14px;
|
|
|
+ width: 0;
|
|
|
+ height: 0;
|
|
|
+ border: 6px solid transparent;
|
|
|
+ border-top-color: #c0c4cc;
|
|
|
+ pointer-events: none;
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- :deep(.hidden-popper) {
|
|
|
- display: none !important;
|
|
|
- }
|
|
|
+ .recipe-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
|
.recipe-input {
|
|
|
- width: 100%;
|
|
|
+ flex: 1;
|
|
|
+ height: 32px;
|
|
|
+ line-height: 32px;
|
|
|
+ padding: 0 12px;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #fff;
|
|
|
+ color: #606266;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .recipe-input-one {
|
|
|
+ flex: 1;
|
|
|
height: 32px;
|
|
|
line-height: 32px;
|
|
|
- padding: 0 12px;
|
|
|
+ padding: 0 1px;
|
|
|
border: 1px solid #dcdfe6;
|
|
|
- border-radius: 4px;
|
|
|
- cursor: pointer;
|
|
|
- color: #606266;
|
|
|
- background-color: #fff;
|
|
|
- position: relative;
|
|
|
- user-select: none; // 防止文本被选中
|
|
|
-
|
|
|
- &:hover {
|
|
|
- border-color: #409eff;
|
|
|
- }
|
|
|
-
|
|
|
- &::after {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- right: 8px;
|
|
|
- top: 14px;
|
|
|
- width: 0;
|
|
|
- height: 0;
|
|
|
- border: 6px solid transparent;
|
|
|
- border-top-color: #c0c4cc;
|
|
|
- pointer-events: none; // 防止箭头影响点击
|
|
|
- }
|
|
|
-
|
|
|
- &.add-more {
|
|
|
- margin-top: 8px;
|
|
|
- color: #909399;
|
|
|
- border-style: dashed;
|
|
|
- }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- .recipe-list {
|
|
|
- .recipe-input {
|
|
|
- width: 100%;
|
|
|
- height: 32px;
|
|
|
- line-height: 32px;
|
|
|
- padding: 0 12px;
|
|
|
- border: 1px solid #dcdfe6;
|
|
|
- border-radius: 4px;
|
|
|
- cursor: pointer;
|
|
|
- color: #606266;
|
|
|
- background-color: #fff;
|
|
|
- position: relative;
|
|
|
- user-select: none;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- border-color: #409eff;
|
|
|
- }
|
|
|
-
|
|
|
- &::after {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- right: 8px;
|
|
|
- top: 14px;
|
|
|
- width: 0;
|
|
|
- height: 0;
|
|
|
- border: 6px solid transparent;
|
|
|
- border-top-color: #c0c4cc;
|
|
|
- pointer-events: none;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .recipe-item {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 8px;
|
|
|
- margin-bottom: 8px;
|
|
|
-
|
|
|
- .recipe-input {
|
|
|
- flex: 1;
|
|
|
- height: 32px;
|
|
|
- line-height: 32px;
|
|
|
- padding: 0 12px;
|
|
|
- border: 1px solid #dcdfe6;
|
|
|
- border-radius: 4px;
|
|
|
- background: #fff;
|
|
|
- color: #606266;
|
|
|
- cursor: pointer;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- border-color: #409eff;
|
|
|
- }
|
|
|
-
|
|
|
- .recipe-input-one {
|
|
|
- flex: 1;
|
|
|
- height: 32px;
|
|
|
- line-height: 32px;
|
|
|
- padding: 0 1px;
|
|
|
- border: 1px solid #dcdfe6;
|
|
|
-
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .delete-btn {
|
|
|
- flex: none;
|
|
|
- padding: 8px;
|
|
|
- color: #f56c6c;
|
|
|
+ .delete-btn {
|
|
|
+ flex: none;
|
|
|
+ padding: 8px;
|
|
|
+ color: #f56c6c;
|
|
|
|
|
|
- &:hover {
|
|
|
- color: #ff4d4f;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ &:hover {
|
|
|
+ color: #ff4d4f;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- .add-more {
|
|
|
- margin-top: 8px;
|
|
|
- border-style: dashed;
|
|
|
- color: #909399;
|
|
|
+ .add-more {
|
|
|
+ margin-top: 8px;
|
|
|
+ border-style: dashed;
|
|
|
+ color: #909399;
|
|
|
|
|
|
- &:hover {
|
|
|
- border-color: #409eff;
|
|
|
- color: #409eff;
|
|
|
- }
|
|
|
- }
|
|
|
+ &:hover {
|
|
|
+ border-color: #409eff;
|
|
|
+ color: #409eff;
|
|
|
}
|
|
|
-</style>
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|