Sfoglia il codice sorgente

营养设定 膳食治疗

HuRongxin 4 mesi fa
parent
commit
1ea4105830

+ 63 - 0
src/api/patients/dailyMealPlan/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { MealPlanVO, MealPlanForm, MealPlanQuery } from '@/api/patients/dailyMealPlan/types';
+
+/**
+ * 查询日常膳食主列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listDailyMealPlan = (query?: MealPlanQuery): AxiosPromise<MealPlanVO[]> => {
+  return request({
+    url: '/patients/dailyMealPlan/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询日常膳食主详细
+ * @param id
+ */
+export const getMealPlan = (id: string | number): AxiosPromise<MealPlanVO> => {
+  return request({
+    url: '/patients/dailyMealPlan/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增日常膳食主
+ * @param data
+ */
+export const addMealPlan = (data: MealPlanForm) => {
+  return request({
+    url: '/patients/dailyMealPlan',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改日常膳食主
+ * @param data
+ */
+export const updateMealPlan = (data: MealPlanForm) => {
+  return request({
+    url: '/patients/dailyMealPlan',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除日常膳食主
+ * @param id
+ */
+export const delMealPlan = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/patients/dailyMealPlan/' + id,
+    method: 'delete'
+  });
+};

+ 147 - 0
src/api/patients/dailyMealPlan/types.ts

@@ -0,0 +1,147 @@
+export interface MealPlanVO {
+    /**
+     * 主键
+     */
+    id: string | number;
+
+    /**
+     * 看诊类型
+     */
+    type: string;
+
+    /**
+     * 患者ID
+     */
+    patientId: string | number;
+
+    /**
+     * 门诊/住院号
+     */
+    outpatientNo: string;
+
+    /**
+     * 科室ID
+     */
+    deptId: string | number;
+
+    /**
+     * 推荐/配餐开始日期
+     */
+    recommendStartDate: string;
+
+    /**
+     * 推荐/配餐结束日期
+     */
+    recommendEndDate: string;
+
+    /**
+     * 处方状态
+     */
+    status: string;
+
+    /**
+     * 备注
+     */
+    remark: string;
+
+}
+
+export interface MealPlanForm extends BaseEntity {
+    /**
+     * 主键
+     */
+    id?: string | number;
+
+    /**
+     * 看诊类型
+     */
+    type?: string;
+
+    /**
+     * 患者ID
+     */
+    patientId?: string | number;
+
+    /**
+     * 门诊/住院号
+     */
+    outpatientNo?: string;
+
+    /**
+     * 科室ID
+     */
+    deptId?: string | number;
+
+    /**
+     * 推荐/配餐开始日期
+     */
+    recommendStartDate?: string;
+
+    /**
+     * 推荐/配餐结束日期
+     */
+    recommendEndDate?: string;
+
+    /**
+     * 处方状态
+     */
+    status?: string;
+
+    /**
+     * 备注
+     */
+    remark?: string;
+
+    mealRecipeList?: Array<JSON>
+
+}
+
+export interface MealPlanQuery extends PageQuery {
+
+    /**
+     * 看诊类型
+     */
+    type?: string;
+
+    /**
+     * 患者ID
+     */
+    patientId?: string | number;
+
+    /**
+     * 门诊/住院号
+     */
+    outpatientNo?: string;
+
+    /**
+     * 科室ID
+     */
+    deptId?: string | number;
+
+    /**
+     * 推荐/配餐开始日期
+     */
+    recommendStartDate?: string;
+
+    /**
+     * 推荐/配餐结束日期
+     */
+    recommendEndDate?: string;
+
+    /**
+     * 处方状态
+     */
+    status?: string;
+
+    searchValue?: string;
+
+    dateRange?: string[];
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/patients/dailyMealRecipe/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { MealRecipeVO, MealRecipeForm, MealRecipeQuery } from '@/api/patients/dailyMealRecipe/types';
+
+/**
+ * 查询日常膳食食谱明细列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listMealRecipe = (query?: MealRecipeQuery): AxiosPromise<MealRecipeVO[]> => {
+  return request({
+    url: '/patients/dailyMealRecipe/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询日常膳食食谱明细详细
+ * @param id
+ */
+export const getMealRecipe = (id: string | number): AxiosPromise<MealRecipeVO> => {
+  return request({
+    url: '/patients/dailyMealRecipe/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增日常膳食食谱明细
+ * @param data
+ */
+export const addMealRecipe = (data: MealRecipeForm) => {
+  return request({
+    url: '/patients/dailyMealRecipe',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改日常膳食食谱明细
+ * @param data
+ */
+export const updateMealRecipe = (data: MealRecipeForm) => {
+  return request({
+    url: '/patients/dailyMealRecipe',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除日常膳食食谱明细
+ * @param id
+ */
+export const delMealRecipe = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/patients/dailyMealRecipe/' + id,
+    method: 'delete'
+  });
+};

+ 206 - 0
src/api/patients/dailyMealRecipe/types.ts

@@ -0,0 +1,206 @@
+export interface MealRecipeVO {
+    /**
+     * 主键
+     */
+    id: string | number;
+
+    /**
+     * 日常膳食主表ID
+     */
+    planId: string | number;
+
+    /**
+     * 食谱序号(1-7)
+     */
+    recipeNo: number;
+
+    /**
+     * 餐次(如早餐/午餐/晚餐/早加/晚加等)
+     */
+    mealTime: string;
+
+    /**
+     * 用餐时间
+     */
+    eatTime: string;
+
+    /**
+     * 食物id
+     */
+    foodId: string | number;
+
+    /**
+     * 食物名称
+     */
+    foodName: string;
+
+    /**
+     * 食谱分类Id
+     */
+    foodCategoryId: string | number;
+
+    /**
+     * 食谱分类
+     */
+    foodCategoryName: string;
+
+    /**
+     * 食物重量(g)
+     */
+    foodWeight: number;
+
+    /**
+     * 热量(kcal)
+     */
+    calorie: number;
+
+    /**
+     * 份数
+     */
+    amount: number;
+
+    /**
+     * 金额
+     */
+    price: number;
+
+}
+
+export interface MealRecipeForm extends BaseEntity {
+    /**
+     * 主键
+     */
+    id?: string | number;
+
+    /**
+     * 日常膳食主表ID
+     */
+    planId?: string | number;
+
+    /**
+     * 食谱序号(1-7)
+     */
+    recipeNo?: number;
+
+    /**
+     * 餐次(如早餐/午餐/晚餐/早加/晚加等)
+     */
+    mealTime?: string;
+
+    /**
+     * 用餐时间
+     */
+    eatTime?: string;
+
+    /**
+     * 食物id
+     */
+    foodId?: string | number;
+
+    /**
+     * 食物名称
+     */
+    foodName?: string;
+
+    /**
+     * 食谱分类Id
+     */
+    foodCategoryId?: string | number;
+
+    /**
+     * 食谱分类
+     */
+    foodCategoryName?: string;
+
+    /**
+     * 食物重量(g)
+     */
+    foodWeight?: number;
+
+    /**
+     * 热量(kcal)
+     */
+    calorie?: number;
+
+    /**
+     * 份数
+     */
+    amount?: number;
+
+    /**
+     * 金额
+     */
+    price?: number;
+
+}
+
+export interface MealRecipeQuery extends PageQuery {
+
+    /**
+     * 日常膳食主表ID
+     */
+    planId?: string | number;
+
+    /**
+     * 食谱序号(1-7)
+     */
+    recipeNo?: number;
+
+    /**
+     * 餐次(如早餐/午餐/晚餐/早加/晚加等)
+     */
+    mealTime?: string;
+
+    /**
+     * 用餐时间
+     */
+    eatTime?: string;
+
+    /**
+     * 食物id
+     */
+    foodId?: string | number;
+
+    /**
+     * 食物名称
+     */
+    foodName?: string;
+
+    /**
+     * 食谱分类Id
+     */
+    foodCategoryId?: string | number;
+
+    /**
+     * 食谱分类
+     */
+    foodCategoryName?: string;
+
+    /**
+     * 食物重量(g)
+     */
+    foodWeight?: number;
+
+    /**
+     * 热量(kcal)
+     */
+    calorie?: number;
+
+    /**
+     * 份数
+     */
+    amount?: number;
+
+    /**
+     * 金额
+     */
+    price?: number;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/patients/hospitalMealPlan/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { MealPlanVO, MealPlanForm, MealPlanQuery } from '@/api/patients/hospitalMealPlan/types';
+
+/**
+ * 查询院内膳食主列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listMealPlan = (query?: MealPlanQuery): AxiosPromise<MealPlanVO[]> => {
+  return request({
+    url: '/patients/hospitalMealPlan/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询院内膳食主详细
+ * @param id
+ */
+export const getMealPlan = (id: string | number): AxiosPromise<MealPlanVO> => {
+  return request({
+    url: '/patients/hospitalMealPlan/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增院内膳食
+ * @param data
+ */
+export const addMealPlan = (data: MealPlanForm) => {
+  return request({
+    url: '/patients/hospitalMealPlan',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改院内膳食
+ * @param data
+ */
+export const updateMealPlan = (data: MealPlanForm) => {
+  return request({
+    url: '/patients/hospitalMealPlan',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除院内膳食
+ * @param id
+ */
+export const delMealPlan = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/patients/hospitalMealPlan/' + id,
+    method: 'delete'
+  });
+};

+ 147 - 0
src/api/patients/hospitalMealPlan/types.ts

@@ -0,0 +1,147 @@
+export interface MealPlanVO {
+    /**
+     * 主键
+     */
+    id: string | number;
+
+    /**
+     * 看诊类型
+     */
+    type: string;
+
+    /**
+     * 患者ID
+     */
+    patientId: string | number;
+
+    /**
+     * 门诊/住院号
+     */
+    outpatientNo: string;
+
+    /**
+     * 科室ID
+     */
+    deptId: string | number;
+
+    /**
+     * 推荐/配餐开始日期
+     */
+    recommendStartDate: string;
+
+    /**
+     * 推荐/配餐结束日期
+     */
+    recommendEndDate: string;
+
+    /**
+     * 处方状态
+     */
+    status: string;
+
+    /**
+     * 备注
+     */
+    remark: string;
+
+}
+
+export interface MealPlanForm extends BaseEntity {
+    /**
+     * 主键
+     */
+    id?: string | number;
+
+    /**
+     * 看诊类型
+     */
+    type?: string;
+
+    /**
+     * 患者ID
+     */
+    patientId?: string | number;
+
+    /**
+     * 门诊/住院号
+     */
+    outpatientNo?: string;
+
+    /**
+     * 科室ID
+     */
+    deptId?: string | number;
+
+    /**
+     * 推荐/配餐开始日期
+     */
+    recommendStartDate?: string;
+
+    /**
+     * 推荐/配餐结束日期
+     */
+    recommendEndDate?: string;
+
+    /**
+     * 处方状态
+     */
+    status?: string;
+
+    /**
+     * 备注
+     */
+    remark?: string;
+
+    mealRecipeList?: Array<JSON>
+
+}
+
+export interface MealPlanQuery extends PageQuery {
+
+    /**
+     * 看诊类型
+     */
+    type?: string;
+
+    /**
+     * 患者ID
+     */
+    patientId?: string | number;
+
+    /**
+     * 门诊/住院号
+     */
+    outpatientNo?: string;
+
+    /**
+     * 科室ID
+     */
+    deptId?: string | number;
+
+    /**
+     * 推荐/配餐开始日期
+     */
+    recommendStartDate?: string;
+
+    /**
+     * 推荐/配餐结束日期
+     */
+    recommendEndDate?: string;
+
+    /**
+     * 处方状态
+     */
+    status?: string;
+
+    searchValue?: string;
+
+    dateRange?: string[];
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 63 - 0
src/api/patients/hospitalMealRecipe/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { MealRecipeVO, MealRecipeForm, MealRecipeQuery } from '@/api/patients/hospitalMealRecipe/types';
+
+/**
+ * 查询院内膳食食谱明细列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listMealRecipe = (query?: MealRecipeQuery): AxiosPromise<MealRecipeVO[]> => {
+  return request({
+    url: '/patients/hospitalMealRecipe/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询院内膳食食谱明细详细
+ * @param id
+ */
+export const getMealRecipe = (id: string | number): AxiosPromise<MealRecipeVO> => {
+  return request({
+    url: '/patients/hospitalMealRecipe/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增院内膳食食谱明细
+ * @param data
+ */
+export const addMealRecipe = (data: MealRecipeForm) => {
+  return request({
+    url: '/patients/hospitalMealRecipe',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改院内膳食食谱明细
+ * @param data
+ */
+export const updateMealRecipe = (data: MealRecipeForm) => {
+  return request({
+    url: '/patients/hospitalMealRecipe',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除院内膳食食谱明细
+ * @param id
+ */
+export const delMealRecipe = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/patients/hospitalMealRecipe/' + id,
+    method: 'delete'
+  });
+};

+ 206 - 0
src/api/patients/hospitalMealRecipe/types.ts

@@ -0,0 +1,206 @@
+export interface MealRecipeVO {
+  /**
+   * 主键
+   */
+  id: string | number;
+
+  /**
+   * 院内膳食主表ID
+   */
+  planId: string | number;
+
+  /**
+   * 食谱序号(1-7)
+   */
+  recipeNo: number;
+
+  /**
+   * 餐次(如早餐/午餐/晚餐/早加/晚加等)
+   */
+  mealTime: string;
+
+  /**
+   * 用餐时间
+   */
+  eatTime: string;
+
+  /**
+   * 食物名称
+   */
+  foodName: string;
+
+  /**
+   * 食物id
+   */
+  foodId: string | number;
+
+  /**
+   * 食谱分类Id
+   */
+  foodCategoryId: string | number;
+
+  /**
+   * 食谱分类
+   */
+  foodCategoryName: string;
+
+  /**
+   * 食物重量(g)
+   */
+  foodWeight: number;
+
+  /**
+   * 热量(kcal)
+   */
+  calorie: number;
+
+  /**
+   * 份数
+   */
+  amount: number;
+
+  /**
+   * 金额
+   */
+  price: number;
+
+}
+
+export interface MealRecipeForm extends BaseEntity {
+  /**
+   * 主键
+   */
+  id?: string | number;
+
+  /**
+   * 院内膳食主表ID
+   */
+  planId?: string | number;
+
+  /**
+   * 食谱序号(1-7)
+   */
+  recipeNo?: number;
+
+  /**
+   * 餐次(如早餐/午餐/晚餐/早加/晚加等)
+   */
+  mealTime?: string;
+
+  /**
+   * 用餐时间
+   */
+  eatTime?: string;
+
+  /**
+   * 食物名称
+   */
+  foodName?: string;
+
+  /**
+   * 食物id
+   */
+  foodId?: string | number;
+
+  /**
+   * 食谱分类Id
+   */
+  foodCategoryId?: string | number;
+
+  /**
+   * 食谱分类
+   */
+  foodCategoryName?: string;
+
+  /**
+   * 食物重量(g)
+   */
+  foodWeight?: number;
+
+  /**
+   * 热量(kcal)
+   */
+  calorie?: number;
+
+  /**
+   * 份数
+   */
+  amount?: number;
+
+  /**
+   * 金额
+   */
+  price?: number;
+
+}
+
+export interface MealRecipeQuery extends PageQuery {
+
+  /**
+   * 院内膳食主表ID
+   */
+  planId?: string | number;
+
+  /**
+   * 食谱序号(1-7)
+   */
+  recipeNo?: number;
+
+  /**
+   * 餐次(如早餐/午餐/晚餐/早加/晚加等)
+   */
+  mealTime?: string;
+
+  /**
+   * 用餐时间
+   */
+  eatTime?: string;
+
+  /**
+   * 食物名称
+   */
+  foodName?: string;
+
+  /**
+   * 食物id
+   */
+  foodId?: string | number;
+
+  /**
+   * 食谱分类Id
+   */
+  foodCategoryId?: string | number;
+
+  /**
+   * 食谱分类
+   */
+  foodCategoryName?: string;
+
+  /**
+   * 食物重量(g)
+   */
+  foodWeight?: number;
+
+  /**
+   * 热量(kcal)
+   */
+  calorie?: number;
+
+  /**
+   * 份数
+   */
+  amount?: number;
+
+  /**
+   * 金额
+   */
+  price?: number;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 8 - 0
src/api/system/recipeCategory/index.ts

@@ -16,6 +16,14 @@ export const listRecipeCategory = (query?: RecipeCategoryQuery): AxiosPromise<Re
   });
 };
 
+export const queryRecipeCategoryList = (query?: RecipeCategoryQuery): AxiosPromise<RecipeCategoryVO[]> => {
+  return request({
+    url: '/system/recipeCategory/queryList',
+    method: 'get',
+    params: query
+  });
+};
+
 /**
  * 查询食谱分类管理详细
  * @param recipeCategoryId

+ 92 - 10
src/views/patients/dietTherapy/addBalance.vue

@@ -46,9 +46,15 @@
           <div class="label">禁忌食材:</div>
         </el-col>
         <el-col :span="12">
-          <el-select v-model="form.forbiddenFood" placeholder="请选择" style="width: 100%">
-            <el-option label="请选择" value="" />
-          </el-select>
+          <div class="forbidden-food-select" @click="openFoodDialog">
+            <div class="selected-foods" v-if="form.forbiddenFoods?.length">
+              <el-tag v-for="food in form.forbiddenFoods" :key="food.id" closable size="small" @close="removeFood(food)">
+                {{ food.name }}
+              </el-tag>
+            </div>
+            <div class="placeholder" v-else>请选择</div>
+            <el-icon class="select-icon"><ArrowDown /></el-icon>
+          </div>
         </el-col>
       </el-row>
     </el-card>
@@ -91,20 +97,20 @@
           <div class="label">体力活动:</div>
         </el-col>
         <el-col :span="22">
-          <el-radio-group v-model="form.activity">
-            <el-radio label="1">
+          <el-radio-group :model-value="form.activity">
+            <el-radio label="1" @click="(e) => onActivityLevelChange('1', e)">
               <el-icon><CircleCheck /></el-icon>
               静态活动
             </el-radio>
-            <el-radio label="2">
+            <el-radio label="2" @click="(e) => onActivityLevelChange('2', e)">
               <el-icon><CircleCheck /></el-icon>
               轻体力活动
             </el-radio>
-            <el-radio label="3">
+            <el-radio label="3" @click="(e) => onActivityLevelChange('3', e)">
               <el-icon><CircleCheck /></el-icon>
               中体力活动
             </el-radio>
-            <el-radio label="4">
+            <el-radio label="4" @click="(e) => onActivityLevelChange('4', e)">
               <el-icon><CircleCheck /></el-icon>
               重体力活动
             </el-radio>
@@ -184,11 +190,21 @@
       </div>
     </div>
   </div>
+
+  <!-- 添加弹窗组件 -->
+  <CommonDialog ref="foodDialogRef" type="food" title="添加食材" @confirm="handleFoodConfirm" @cancel="handleFoodCancel" />
 </template>
 
 <script setup lang="ts">
 import {ref} from 'vue';
-import {QuestionFilled, CircleCheck, ArrowLeft} from '@element-plus/icons-vue';
+import {QuestionFilled, CircleCheck, ArrowLeft, ArrowDown} from '@element-plus/icons-vue';
+import CommonDialog from './components/CommonDialog.vue';
+
+interface Food {
+  id: number;
+  name: string;
+  ingredients: string;
+}
 
 const emit = defineEmits(['goBack']);
 
@@ -200,7 +216,7 @@ const form = ref({
   gender: '女',
   height: '165.00',
   weight: '55.00',
-  forbiddenFood: '',
+  forbiddenFoods: [] as Food[],
   dailyEnergy: '1800.00',
   activity: '2',
   stressState: 'low',
@@ -211,6 +227,34 @@ const form = ref({
   protein: '15',
   fat: '25'
 });
+
+const dialogRef = ref<InstanceType<typeof CommonDialog> | null>(null);
+const foodDialogRef = ref<InstanceType<typeof CommonDialog> | null>(null);
+
+const onActivityLevelChange = (val: string, e: Event) => {
+  e.stopPropagation();
+  e.preventDefault();
+  form.value.activity = val;
+};
+// 打开食材选择弹窗
+const openFoodDialog = () => {
+  foodDialogRef.value?.openDialog('food', form.value.forbiddenFoods);
+};
+
+// 处理食材确认
+const handleFoodConfirm = (selectedFoods: Food[]) => {
+  form.value.forbiddenFoods = selectedFoods;
+};
+
+// 处理食材取消
+const handleFoodCancel = () => {
+  // 可以不做任何处理
+};
+
+// 移除食材
+const removeFood = (food: Food) => {
+  form.value.forbiddenFoods = form.value.forbiddenFoods.filter((f) => f.id !== food.id);
+};
 </script>
 
 <style lang="scss" scoped>
@@ -406,4 +450,42 @@ const form = ref({
     }
   }
 }
+
+.forbidden-food-select {
+  width: 100%;
+  min-height: 32px;
+  padding: 4px 30px 4px 12px;
+  border: 1px solid #dcdfe6;
+  border-radius: 4px;
+  cursor: pointer;
+  position: relative;
+  background-color: #fff;
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  align-items: center;
+
+  &:hover {
+    border-color: #409eff;
+  }
+
+  .selected-foods {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+  }
+
+  .placeholder {
+    color: #909399;
+  }
+
+  .select-icon {
+    position: absolute;
+    right: 8px;
+    top: 50%;
+    transform: translateY(-50%);
+    font-size: 14px;
+    color: #c0c4cc;
+  }
+}
 </style>

+ 939 - 416
src/views/patients/dietTherapy/addDaily.vue

@@ -1,439 +1,962 @@
 <template>
-  <div class="container">
-    <!-- 中间卡片 -->
-    <div class="add-hospital-wrapper">
-      <div class="header-bar">
-        <el-button @click="goBack" type="primary" plain class="back-btn">
-          <el-icon><ArrowLeft /></el-icon>
-          返回
-        </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="dateRange"
-              type="daterange"
-              range-separator="-"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              style="width: 100%"
-            />
-          </el-col>
-          <el-col :span="3">
-            <el-button type="primary">新增食谱</el-button>
-          </el-col>
-          <el-col :span="3">
-            <el-button>食谱模板</el-button>
-          </el-col>
-          <el-col :span="3">
-            <el-button type="danger">全部删除</el-button>
-          </el-col>
-        </el-row>
-      </div>
-      <div class="tab-bar">
-        <el-button type="success" plain icon="Document" class="tab-btn" style="background: #4cd964; color: #fff; border: none">食谱一</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="100" align="center" />
-        <el-table-column prop="time" label="用餐时间" width="140" 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="食谱名称">
-          <template #default="{row}">
-            <el-select v-model="row.menuName" placeholder="请选择" style="width: 100%">
-              <el-option label="请选择" value="" />
-            </el-select>
-          </template>
-        </el-table-column>
-        <el-table-column prop="menuCategory" label="食谱分类" width="120" align="center">
-          <template #default="{row}">
-            <el-tag size="small" type="info" v-if="row.menuCategory">{{ row.menuCategory }}</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="count" label="份数" width="150" align="center">
-          <template #default="{row}">
-            <el-input-number v-model="row.count" :min="1" :max="99" size="small" />
-          </template>
-        </el-table-column>
-        <el-table-column prop="weight" label="食材重量" width="120" align="center">
-          <template #default="{row}">
-            <span>{{ row.weight || '-' }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="calorie" label="热量" width="120" align="center">
-          <template #default="{row}">
-            <span>{{ row.calorie || '-' }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="amount" label="金额" width="120" 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>
-      </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 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-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="热量占比">
+                <el-table-column prop="meal" label="餐次" width="200px" align="center" />
+                <el-table-column prop="time" label="用餐时间" width="260px" align="center">
                     <template #default="{row}">
-                      <span>{{ row.ratio }}%</span>
+                        <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="reference" label="参考值">
+                </el-table-column>
+                <el-table-column prop="menuName" label="食谱名称" width="350px" align="center">
                     <template #default="{row}">
-                      <span>{{ row.reference }}</span>
+                        <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>
-              </el-col>
-
-              <el-col :span="10">
-                <div class="chart-container">
-                  <div class="chart-title">三大营养素元素质量占比</div>
-                  <div id="nutritionChart" ref="chartRef" 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>存为草稿</el-button>
-        <el-button type="primary">提交</el-button>
-      </div>
+                </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 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>
+
+        </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>
+            </div>
+        </div>
     </div>
-  </div>
-</template>
 
+    <!-- 添加弹窗组件 -->
+    <CommonDialog ref="dialogRef" @confirm="handleRecipeConfirm" @cancel="handleRecipeCancel" />
+    <!-- 食谱模板弹窗组件 -->
+    <RecipeTemplate ref="recipeTemplateRef" @confirm="handleRecipeTemplateConfirm" />
+</template>
 <script setup lang="ts">
-import {ref, onMounted} from 'vue';
-import * as echarts from 'echarts';
-import {ArrowLeft} from '@element-plus/icons-vue';
-
-const dateRange = ref([]);
-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 tableData = ref([
-  {meal: '早餐', time: '08:00', menuName: '', menuCategory: '', count: 1, weight: '', calorie: '', amount: ''},
-  {meal: '早加', time: '08:30', menuName: '', menuCategory: '', count: 1, weight: '', calorie: '', amount: ''},
-  {meal: '中餐', time: '12:00', menuName: '', menuCategory: '', count: 1, weight: '', calorie: '', amount: ''},
-  {meal: '中加', time: '12:30', menuName: '', menuCategory: '', count: 1, weight: '', calorie: '', amount: ''},
-  {meal: '晚餐', time: '18:00', menuName: '', menuCategory: '', count: 1, weight: '', calorie: '', amount: ''},
-  {meal: '晚加', time: '18:30', menuName: '', menuCategory: '', count: 1, weight: '', calorie: '', amount: ''}
-]);
-
-const chartRef = ref<HTMLElement | null>(null);
-let myChart: echarts.ECharts | null = null;
-
-// 初始化图表
-const initChart = () => {
-  if (!chartRef.value) return;
-
-  myChart = echarts.init(chartRef.value);
-  const option = {
-    tooltip: {
-      show: false
-    },
-    legend: {
-      show: false
-    },
-    color: ['#00D1D1', '#FFC53D', '#2F54EB'],
-    series: [
-      {
-        name: '营养元素',
-        type: 'pie',
-        radius: ['70%', '90%'],
-        avoidLabelOverlap: false,
-        silent: true,
-        itemStyle: {
-          borderWidth: 0
-        },
-        label: {
-          show: false
-        },
-        emphasis: {
-          disabled: true
-        },
-        labelLine: {
-          show: false
-        },
-        data: [
-          {value: 33.33, name: '蛋白质'},
-          {value: 33.33, name: '脂肪'},
-          {value: 33.34, name: '碳水化合物'}
-        ]
-      }
-    ]
-  };
-
-  myChart.setOption(option);
-};
-
-const emit = defineEmits(['goBack']);
-// 返回上一页按钮
-const goBack = () => {
-  emit('goBack');
-};
-
-onMounted(() => {
-  initChart();
-  window.addEventListener('resize', () => {
-    myChart?.resize();
-  });
-});
-</script>
+    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', 'submitDaily']);
+    import { MealRecipeVO, MealRecipeQuery, MealRecipeForm } from '@/api/patients/dailyMealRecipe/types';
+    import { ElMessage } from 'element-plus';
+
+    const props = defineProps({
+        patientInfo: {
+            type: Object,
+            required: true,
+            default: () => ({
+                id: '',
+                name: '',
+                age: '',
+                gender: ''
+            })
+        }
+    });
+    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;
+        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 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: '', recipes: [] },
+        { meal: '早加', time: '08:30', foodId: '', menuName: '', categoryId: '', categoryName: '', count: 1, weight: '', calorie: '', recipes: [] },
+        { meal: '中餐', time: '12:00', foodId: '', menuName: '', categoryId: '', categoryName: '', count: 1, weight: '', calorie: '', recipes: [] },
+        { meal: '中加', time: '12:30', foodId: '', menuName: '', categoryId: '', categoryName: '', count: 1, weight: '', calorie: '', recipes: [] },
+        { meal: '晚餐', time: '18:00', foodId: '', menuName: '', categoryId: '', categoryName: '', count: 1, weight: '', calorie: '', recipes: [] },
+        { meal: '晚加', time: '18:30', foodId: '', menuName: '', categoryId: '', categoryName: '', count: 1, weight: '', calorie: '', 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,
+                    });
+                });
+            }
+        });
+        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);
+            }
+        }
+        return (weight * count).toFixed(4);
+    }
+
+    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);
+            }
+        }
+        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,
+            mealRecipeList
+        };
+
+
+        emit('submitDaily', 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) {
+                // 解析失败可忽略
+            }
+        }
+    }
+
+    // 监听患者切换自动加载草稿
+    watch(() => props.patientInfo.id, () => {
+        loadDraft();
+    }, { immediate: true });
+
+    // 切换食谱
+    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 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[]) => {
+        console.log('Confirming selection:', selectedRecipes); // 添加日志
+        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 = () => {
+        console.log('Cancelling selection'); // 添加日志
+    };
+
+    // 移除食谱
+    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);
+                }
+            });
+        });
+    };
+
+    // 更新行数据的逻辑优化
+    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 totalWeight = 0;
+            let totalCalorie = 0;
+            row.recipes.forEach((recipe) => {
+                const weight = parseFloat(recipe.weight ?.replace(/[^\d.]/g, '') || '0');
+                const calorie = parseFloat(recipe.calorie ?.replace(/[^\d.]/g, '') || '0');
+                totalWeight += weight;
+                totalCalorie += calorie;
+            });
+
+            // 乘以份数
+            totalWeight = totalWeight * row.count;
+            totalCalorie = totalCalorie * row.count;
+
+            // 更新行数据
+            row.weight = totalWeight.toFixed(4) + 'g';
+            row.calorie = totalCalorie.toFixed(4);
+        } else {
+            // 清空数据
+            row.foodId = '';
+            row.menuName = '';
+            row.categoryId = '';
+            row.weight = '';
+            row.calorie = '';
+            row.recipes = [];
+        }
+    };
+
+    // 分页相关
+    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();
+    });
+
+</script>
 <style lang="scss" scoped>
-.container {
-  position: relative;
-  min-height: 100vh;
-  padding-bottom: 76px; // 底部按钮的高度 + padding
-
-  .el-card {
-    margin-top: 16px;
-    height: 500px;
-  }
-}
-
-.add-hospital-wrapper {
-  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;
-    display: flex;
-    align-items: center;
-    .el-icon {
-      margin-right: 4px;
+    .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;
+        }
     }
-  }
-  .title {
-    font-size: 20px;
-    font-weight: 600;
-  }
-}
-
-.form-bar {
-  display: flex;
-  align-items: center;
-  margin-bottom: 12px;
-  .ml-2 {
-    margin-left: 12px;
-  }
-}
-
-.tab-bar {
-  margin-bottom: 8px;
-}
-
-.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;
+
+    .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;
+            }
+
+            &: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);
+                }
+            }
+        }
+    }
+
+    .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;
+        }
     }
-  }
-}
-
-.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 {
-    width: 180px;
-    height: 180px;
-    margin: 0 auto;
-  }
-
-  .chart-legend {
-    display: flex;
-    justify-content: center;
-    gap: 32px;
-    margin-top: 24px;
-
-    .legend-item {
-      display: flex;
-      align-items: center;
-      gap: 8px;
-      color: #666;
-      font-size: 13px;
-
-      .color-block {
-        width: 12px;
-        height: 12px;
-        border-radius: 2px;
-
-        &.protein {
-          background-color: #00d1d1;
+
+    .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;
+                }
+            }
         }
+    }
 
-        &.fat {
-          background-color: #ffc53d;
+    .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;
         }
 
-        &.carbs {
-          background-color: #2f54eb;
+        .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;
+                    }
+
+                    &.fat {
+                        background-color: #ffc53d;
+                    }
+
+                    &.carbs {
+                        background-color: #2f54eb;
+                    }
+                }
+            }
+        }
+    }
+
+    .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;
+            }
+        }
+    }
+
+    :deep(.el-main) {
+        overflow-x: hidden;
     }
-  }
-}
-
-.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;
+
+    :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;
+        }
     }
-  }
-}
 
-:deep(.el-main) {
-  overflow-x: hidden;
-}
-</style>
+    .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;
+
+                &:hover {
+                    color: #ff4d4f;
+                }
+            }
+        }
+
+        .add-more {
+            margin-top: 8px;
+            border-style: dashed;
+            color: #909399;
+
+            &:hover {
+                border-color: #409eff;
+                color: #409eff;
+            }
+        }
+    }
+</style>

+ 970 - 401
src/views/patients/dietTherapy/addHospital.vue

@@ -1,423 +1,992 @@
 <template>
-  <div class="container">
-    <!-- 中间卡片 -->
-    <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="dateRange"
-              type="daterange"
-              range-separator="-"
-              start-placeholder="开始日期"
-              end-placeholder="结束日期"
-              style="width: 100%"
-            />
-          </el-col>
-          <el-col :span="3">
-            <el-button type="primary">新增食谱</el-button>
-          </el-col>
-          <el-col :span="3">
-            <el-button>食谱模板</el-button>
-          </el-col>
-          <el-col :span="3">
-            <el-button type="danger">全部删除</el-button>
-          </el-col>
-        </el-row>
-      </div>
-      <div class="tab-bar">
-        <el-button type="success" plain icon="Document" class="tab-btn" style="background: #4cd964; color: #fff; border: none">食谱一</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="100" align="center" />
-        <el-table-column prop="time" label="用餐时间" width="140" 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="食谱名称">
-          <template #default="{row}">
-            <el-select v-model="row.menuName" placeholder="请选择" style="width: 100%">
-              <el-option label="请选择" value="" />
-            </el-select>
-          </template>
-        </el-table-column>
-        <el-table-column prop="menuCategory" label="食谱分类" width="120" align="center">
-          <template #default="{row}">
-            <el-tag size="small" type="info" v-if="row.menuCategory">{{ row.menuCategory }}</el-tag>
-          </template>
-        </el-table-column>
-        <el-table-column prop="count" label="份数" width="150" align="center">
-          <template #default="{row}">
-            <el-input-number v-model="row.count" :min="1" :max="99" size="small" />
-          </template>
-        </el-table-column>
-        <el-table-column prop="weight" label="食材重量" width="120" align="center">
-          <template #default="{row}">
-            <span>{{ row.weight || '-' }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="calorie" label="热量" width="120" align="center">
-          <template #default="{row}">
-            <span>{{ row.calorie || '-' }}</span>
-          </template>
-        </el-table-column>
-        <el-table-column prop="amount" label="金额" width="120" 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>
-      </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 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-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="热量占比">
+                <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}">
-                      <span>{{ row.ratio }}%</span>
+                        <el-input-number v-model="row.count" :min="1" :max="99" @change="updateRowData(row)" size="small" />
                     </template>
-                  </el-table-column>
-                  <el-table-column prop="reference" label="参考值">
+                </el-table-column>
+                <!-- <el-table-column prop="weight" label="食材重量" align="center">
                     <template #default="{row}">
-                      <span>{{ row.reference }}</span>
+                        <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>
-              </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>存为草稿</el-button>
-        <el-button type="primary">提交</el-button>
-      </div>
+                </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>
+            </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>
+            </div>
+        </div>
     </div>
-  </div>
+
+    <!-- 添加弹窗组件 -->
+    <CommonDialog ref="dialogRef" @confirm="handleRecipeConfirm" @cancel="handleRecipeCancel" />
+    <!-- 食谱模板弹窗组件 -->
+    <RecipeTemplate ref="recipeTemplateRef" @confirm="handleRecipeTemplateConfirm" />
 </template>
 <script setup lang="ts">
-import {ref, onMounted} from 'vue';
-import * as echarts from 'echarts';
-
-const dateRange = ref([]);
-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 tableData = ref([
-  {meal: '早餐', time: '08:00', menuName: '', menuCategory: '', count: 1, weight: '', calorie: '', amount: ''},
-  {meal: '早加', time: '08:30', menuName: '', menuCategory: '', count: 1, weight: '', calorie: '', amount: ''},
-  {meal: '中餐', time: '12:00', menuName: '', menuCategory: '', count: 1, weight: '', calorie: '', amount: ''},
-  {meal: '中加', time: '12:30', menuName: '', menuCategory: '', count: 1, weight: '', calorie: '', amount: ''},
-  {meal: '晚餐', time: '18:00', menuName: '', menuCategory: '', count: 1, weight: '', calorie: '', amount: ''},
-  {meal: '晚加', time: '18:30', menuName: '', menuCategory: '', count: 1, weight: '', calorie: '', amount: ''}
-]);
-
-// 初始化图表
-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 emit = defineEmits(['goBack']);
-// 返回上一页按钮
-const goBack = () => {
-  emit('goBack');
-};
-
-onMounted(() => {
-  initChart();
-});
+    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: ''
+            })
+        }
+    });
+    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 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;
+    }
+
+    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);
+    }
+
+    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);
+            }
+        }
+        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,
+            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) {
+                // 解析失败可忽略
+            }
+        }
+    }
+
+
+
+    // 切换食谱
+    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 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);
+    }
+
+    // 更新行数据的逻辑优化
+    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();
+    });
 </script>
 <style lang="scss" scoped>
-.container {
-  position: relative;
-  min-height: 100vh;
-  padding-bottom: 76px; // 底部按钮的高度 + padding
-
-  .el-card {
-    margin-top: 16px;
-    height: 500px;
-  }
-}
-
-.add-hospital-wrapper {
-  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 {
-  display: flex;
-  align-items: center;
-  margin-bottom: 12px;
-  .ml-2 {
-    margin-left: 12px;
-  }
-}
-
-.tab-bar {
-  margin-bottom: 8px;
-}
-
-.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;
+    .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;
+            }
+
+            &: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);
+                }
+            }
+        }
+    }
+
+    .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;
+        }
     }
-  }
-}
-
-.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;
+
+    .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;
         }
 
-        &.fat {
-          background-color: #ffc53d;
+        .chart-content {
+            height: 200px;
+            width: 200px;
         }
 
-        &.carbs {
-          background-color: #2f54eb;
+        .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;
+                    }
+                }
+            }
         }
-      }
     }
-  }
-}
-
-.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;
+
+    .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;
+            }
+        }
+    }
+
+    :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;
+        }
     }
-  }
-}
 
-:deep(.el-main) {
-  overflow-x: hidden;
-}
-</style>
+    .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;
+
+                &:hover {
+                    color: #ff4d4f;
+                }
+            }
+        }
+
+        .add-more {
+            margin-top: 8px;
+            border-style: dashed;
+            color: #909399;
+
+            &:hover {
+                border-color: #409eff;
+                color: #409eff;
+            }
+        }
+    }
+</style>

+ 226 - 18
src/views/patients/dietTherapy/addSmartRec.vue

@@ -14,18 +14,23 @@
       <div class="form-content">
         <div class="diet-type">
           <span class="required">基本膳食</span>
-          <el-checkbox-group v-model="dietTypes">
-            <el-checkbox label="普食">普食</el-checkbox>
-            <el-checkbox label="软食">软食</el-checkbox>
-            <el-checkbox label="半流食">半流食</el-checkbox>
-            <el-checkbox label="流食">流食</el-checkbox>
+          <el-checkbox-group :model-value="dietTypes">
+            <el-checkbox v-for="item in dietOptions" :key="item" :label="item" @click="(e) => onDietTypesChange(item, e)">
+              {{ item }}
+            </el-checkbox>
           </el-checkbox-group>
         </div>
         <div class="forbidden-food">
           <span>禁忌食材</span>
-          <el-select v-model="forbiddenFoods" placeholder="请选择" style="width: 50%">
-            <el-option label="请选择" value="" />
-          </el-select>
+          <div class="food-select" @click="openFoodDialog">
+            <div v-if="forbiddenFoods" class="selected-foods">
+              <el-tag v-for="food in forbiddenFoods.split(',')" :key="food" size="small" closable @close="removeForbiddenFood(food)">
+                {{ food }}
+              </el-tag>
+            </div>
+            <div v-else class="placeholder">请选择</div>
+            <el-icon class="select-icon"><ArrowDown /></el-icon>
+          </div>
         </div>
       </div>
     </el-card>
@@ -58,11 +63,11 @@
         <div class="form-row">
           <div class="form-item physical-activity">
             <span class="required">体力活动</span>
-            <el-radio-group v-model="activityLevel">
-              <el-radio label="静养活动">静养活动</el-radio>
-              <el-radio label="轻体力活动">轻体力活动</el-radio>
-              <el-radio label="中体力活动">中体力活动</el-radio>
-              <el-radio label="重体力活动">重体力活动</el-radio>
+            <el-radio-group :model-value="activityLevel">
+              <el-radio label="静养活动" @click="(e) => onActivityLevelChange('静养活动', e)">静养活动</el-radio>
+              <el-radio label="轻体力活动" @click="(e) => onActivityLevelChange('轻体力活动', e)">轻体力活动</el-radio>
+              <el-radio label="中体力活动" @click="(e) => onActivityLevelChange('中体力活动', e)">中体力活动</el-radio>
+              <el-radio label="重体力活动" @click="(e) => onActivityLevelChange('重体力活动', e)">重体力活动</el-radio>
             </el-radio-group>
           </div>
         </div>
@@ -199,21 +204,51 @@
     <!-- 底部按钮 -->
     <div class="bottom-btn-card">
       <div class="btn-wrapper">
-        <el-button>取消</el-button>
-        <el-button type="primary">生成推荐方案</el-button>
+        <el-button @click="handleCancel">取消</el-button>
+        <el-button type="primary" @click="handleGenerate">生成推荐方案</el-button>
       </div>
     </div>
+
+    <!-- 添加食材选择弹窗 -->
+    <common-dialog ref="foodDialogRef" type="food" title="添加食材" @confirm="handleFoodConfirm" @cancel="handleFoodCancel" />
   </div>
 </template>
 
 <script setup lang="ts">
 import {ref, computed} from 'vue';
-import {ArrowLeft, CircleCheck, QuestionFilled} from '@element-plus/icons-vue';
+import {ArrowLeft, CircleCheck, QuestionFilled, ArrowDown} from '@element-plus/icons-vue';
+import CommonDialog from './components/CommonDialog.vue';
 
 // 基本信息
-const dietTypes = ref(['普食']);
+const dietTypes = ref<string[]>(['普食']); // 确保是数组类型
+const dietOptions = ['普食', '软食', '半流食', '流食'];
 const forbiddenFoods = ref('');
 
+const foodDialogRef = ref();
+
+// 打开食材选择弹窗
+const openFoodDialog = () => {
+  // 将当前已选择的食材传入
+  const currentFoods = forbiddenFoods.value ? forbiddenFoods.value.split(',').map((name) => ({name: name.trim()})) : [];
+  foodDialogRef.value?.openDialog('food', currentFoods);
+};
+
+// 处理食材选择确认
+const handleFoodConfirm = (selectedFoods: any[]) => {
+  forbiddenFoods.value = selectedFoods.map((item) => item.name).join(',');
+};
+
+// 处理食材选择取消
+const handleFoodCancel = () => {
+  console.log('取消选择食材');
+};
+
+// 移除禁忌食材
+const removeForbiddenFood = (food: string) => {
+  const foods = forbiddenFoods.value.split(',');
+  forbiddenFoods.value = foods.filter((f) => f !== food).join(',');
+};
+
 // 热量计算
 const height = ref('165.00');
 const weight = ref('55.00');
@@ -273,6 +308,35 @@ const emit = defineEmits(['goBack']);
 const goBack = () => {
   emit('goBack');
 };
+
+const onDietTypesChange = (item: string, e: Event) => {
+  e.stopPropagation();
+  e.preventDefault();
+  const newVals = toRaw(dietTypes.value);
+  if (newVals.includes(item)) {
+    newVals.splice(newVals.indexOf(item), 1);
+  } else {
+    newVals.push(item);
+  }
+  dietTypes.value = [...new Set(newVals)];
+};
+
+const onActivityLevelChange = (val: string, e: Event) => {
+  e.stopPropagation();
+  e.preventDefault();
+  activityLevel.value = val;
+};
+
+// 按钮处理函数
+const handleCancel = () => {
+  // 取消操作
+  goBack();
+};
+
+const handleGenerate = () => {
+  // 生成推荐方案
+  console.log('生成推荐方案');
+};
 </script>
 
 <style lang="scss" scoped>
@@ -385,15 +449,87 @@ const goBack = () => {
   display: flex;
   align-items: center;
   gap: 16px;
+
+  :deep(.el-checkbox-group) {
+    display: flex;
+    gap: 24px;
+  }
+
+  :deep(.el-checkbox) {
+    margin-right: 0;
+    height: 32px;
+
+    .el-checkbox__label {
+      font-size: 14px;
+      color: #4e5969;
+    }
+
+    .el-checkbox__input {
+      &.is-checked {
+        .el-checkbox__inner {
+          background-color: #409eff;
+          border-color: #409eff;
+        }
+      }
+
+      .el-checkbox__inner {
+        &:hover {
+          border-color: #409eff;
+        }
+      }
+    }
+
+    &:hover {
+      .el-checkbox__label {
+        color: #409eff;
+      }
+    }
+  }
 }
 
 .forbidden-food {
   display: flex;
-  align-items: center;
+  align-items: flex-start;
   gap: 16px;
 
   span {
     min-width: 70px;
+    line-height: 32px;
+  }
+
+  .food-select {
+    flex: 1;
+    min-height: 32px;
+    padding: 4px 30px 4px 12px;
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    cursor: pointer;
+    position: relative;
+    background-color: #fff;
+
+    &:hover {
+      border-color: #409eff;
+    }
+
+    .selected-foods {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+    }
+
+    .placeholder {
+      color: #909399;
+      line-height: 24px;
+    }
+
+    .select-icon {
+      position: absolute;
+      right: 8px;
+      top: 50%;
+      transform: translateY(-50%);
+      font-size: 14px;
+      color: #c0c4cc;
+    }
   }
 }
 
@@ -703,4 +839,76 @@ const goBack = () => {
     }
   }
 }
+
+.food-dialog-content {
+  .search-bar {
+    margin-bottom: 16px;
+
+    .el-input {
+      width: 100%;
+    }
+  }
+
+  .food-select-container {
+    display: flex;
+    gap: 16px;
+    min-height: 400px;
+    margin-bottom: 16px;
+
+    .food-categories {
+      width: 200px;
+      border: 1px solid #e4e7ed;
+      border-radius: 4px;
+      overflow-y: auto;
+    }
+
+    .food-list {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+
+      :deep(.el-table) {
+        flex: 1;
+
+        .selected-row {
+          background-color: #f0f7ff;
+        }
+      }
+
+      .pagination {
+        margin-top: 16px;
+        display: flex;
+        justify-content: flex-end;
+      }
+    }
+  }
+
+  .selected-tags {
+    border-top: 1px solid #e4e7ed;
+    padding-top: 16px;
+    min-height: 32px;
+
+    .selected-title {
+      color: #606266;
+      margin-bottom: 8px;
+    }
+
+    .tags-container {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+
+      .selected-tag {
+        margin-right: 8px;
+        margin-bottom: 8px;
+      }
+    }
+  }
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 16px;
+}
 </style>

+ 577 - 0
src/views/patients/dietTherapy/components/CommonDialog.vue

@@ -0,0 +1,577 @@
+<template>
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="1000px" :modal="true" :close-on-click-modal="false" :close-on-press-escape="false" :show-close="true" append-to-body lock-scroll :z-index="9999" destroy-on-close>
+        <!-- 食谱选择模式 -->
+        <div v-if="dialogType === 'recipe'" class="dialog-body">
+            <div class="recipe-search">
+                <div class="search-wrapper">
+                    <el-input v-model="searchKeyword" placeholder="请输入食谱名称" clearable class="search-input">
+                        <template #prefix>
+                            <el-icon>
+                                <Search />
+                            </el-icon>
+                        </template>
+                    </el-input>
+                    <el-button type="primary" class="search-btn">
+                        <el-icon>
+                            <Search />
+                        </el-icon>
+                        查询
+                    </el-button>
+                </div>
+            </div>
+
+            <div class="recipe-content">
+                <div class="recipe-categories">
+                    <div v-for="category in recipeCategories" :key="category.id" class="category-item" :class="{active: currentCategory === category.id}" @click.stop="selectCategory(category.id)">
+                        {{ category.name }}
+                    </div>
+                </div>
+
+                <div class="recipe-list">
+                    <el-table ref="multipleTable" :data="recipeList" style="width: 100%" @selection-change="handleSelectionChange" border>
+                        <el-table-column type="selection" width="55" />
+                        <el-table-column prop="id" v-if="false" />
+                        <el-table-column prop="name" label="食谱名称" min-width="200" />
+                        <el-table-column prop="ingredients" label="食材名称/用量" min-width="300" show-overflow-tooltip />
+                    </el-table>
+                    <div class="pagination">
+                        <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :total="total" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next" small background @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+                    </div>
+                </div>
+            </div>
+
+            <div class="selected-recipes">
+                已选({{ selectedRecipes.length }}):
+                <el-tag v-for="recipe in selectedRecipes" :key="recipe.id" closable size="small" @close.stop="removeSelectedRecipe(recipe)">
+                    {{ recipe.name }}
+                </el-tag>
+            </div>
+        </div>
+
+        <!-- 食材选择模式 -->
+        <div v-else-if="dialogType === 'food'" class="dialog-body">
+            <div class="food-dialog-content">
+                <div class="search-bar">
+                    <el-input v-model="searchKeyword" placeholder="请输入食材名称" clearable class="search-input" />
+                    <el-button type="primary" class="search-btn">
+                        <el-icon>
+                            <Search />
+                        </el-icon>
+                        查询
+                    </el-button>
+                </div>
+                <div class="food-select-container">
+                    <div class="food-categories">
+                        <el-tree ref="treeRef" :data="foodCategories" :props="defaultProps" @node-click="handleNodeClick" default-expand-all />
+                    </div>
+                    <div class="food-list">
+                        <el-table ref="foodTableRef" :data="foodList" style="width: 100%" @selection-change="handleFoodSelectionChange" :row-class-name="tableRowClassName">
+                            <el-table-column type="selection" width="40" align="center" />
+                            <el-table-column prop="name" label="食材名称" />
+                        </el-table>
+                        <div class="pagination">
+                            <el-pagination v-model:current-page="currentPage" v-model:page-size="pageSize" :total="total" :page-sizes="[20, 50, 100]" layout="total, sizes, prev, pager, next" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
+                        </div>
+                    </div>
+                </div>
+                <div class="selected-tags">
+                    <div class="selected-title">已选({{ selectedFoods.length }}):</div>
+                    <div class="tags-container">
+                        <el-tag v-for="item in selectedFoods" :key="item.name" class="selected-tag" closable @close="handleRemoveTag(item)">
+                            {{ item.name }}
+                        </el-tag>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <template #footer>
+            <span class="dialog-footer">
+                <el-button @click="handleCancel">取消</el-button>
+                <el-button type="primary" @click="handleConfirm">确定</el-button>
+            </span>
+        </template>
+    </el-dialog>
+</template>
+
+<script setup lang="ts">
+    import { ref, computed, nextTick } from 'vue';
+    import { Search } from '@element-plus/icons-vue';
+    import { ElTree } from 'element-plus';
+    import { ElTable } from 'element-plus';
+
+    interface Recipe {
+        id: number;
+        name: string;
+        categoryId: number;
+        categoryName: string;
+        ingredients: string;
+    }
+
+    interface Category {
+        id: number;
+        name: string;
+    }
+
+    interface Food {
+        name: string;
+    }
+
+    interface Props {
+        type ? : 'recipe' | 'food';
+        title ? : string;
+    }
+
+    const props = withDefaults(defineProps < Props > (), {
+        type: 'recipe',
+        title: '添加食谱'
+    });
+
+    const emit = defineEmits < {
+        (e: 'confirm', data: Recipe[] | Food[]): void;
+        (e: 'cancel'): void;
+    } > ();
+
+    const dialogType = ref < 'recipe' | 'food' > ('recipe');
+    const dialogTitle = computed(() => props.title);
+    const dialogVisible = ref(false);
+    const searchKeyword = ref('');
+    const currentCategory = ref('');
+    const currentPage = ref(1);
+    const pageSize = ref(10);
+    const total = ref(1869);
+    const selectedRecipes = ref < Recipe[] > ([]);
+    const selectedFoods = ref < Food[] > ([]);
+    const multipleTable = ref();
+    const treeRef = ref < InstanceType < typeof ElTree >> ();
+    const foodTableRef = ref < InstanceType < typeof ElTable >> ();
+
+    // 食材分类树配置
+    const defaultProps = {
+        children: 'children',
+        label: 'label'
+    };
+
+    // 食材分类数据
+    const foodCategories = ref([{
+            label: '调料类',
+            children: [{ label: '中药' }, { label: '调料' }, { label: '盐' }]
+        },
+        {
+            label: '油脂类',
+            children: [{ label: '动物油' }, { label: '植物油' }]
+        },
+        {
+            label: '奶及奶制品',
+            children: [{ label: '奶饮料' }, { label: '饮料' }, { label: '植物奶制品' }, { label: '动物奶制品' }, { label: '动物奶' }]
+        }
+    ]);
+
+    // 食材列表数据
+    const foodList = ref < Food[] > ([
+        { name: '苦菊1212' },
+        { name: '水蛋蛋' },
+        { name: '紫甘蓝' },
+        { name: '三色米饭' },
+        { name: '小番茄' },
+        { name: '玉米粒' },
+        { name: '白菜花' },
+        { name: '鸡胸肉' },
+        { name: '罗莎红生菜' }
+    ]);
+
+    // 模拟食谱分类数据
+    const recipeCategories = ref < Category[] > ([
+        { id: 11, name: '普食' },
+        { id: 12, name: '软食' },
+        { id: 13, name: '半流食' },
+        { id: 14, name: '流食' },
+        { id: 15, name: '低脂肪膳食' },
+        { id: 16, name: '限制蛋白膳食' },
+        { id: 17, name: '降血压食谱' },
+        { id: 18, name: '肾病食谱' },
+        { id: 19, name: '妊娠期食谱' },
+        { id: 20, name: '月子餐' },
+        { id: 21, name: '肝胆病食谱' },
+        { id: 22, name: '学龄儿童生长迟缓营养餐' }
+    ]);
+
+    // 模拟食谱列表数据
+    const recipeList = ref < Recipe[] > ([
+        { id: 1, name: '蛋羹',categoryId:11,categoryName:"普食", ingredients: '鸡蛋(白皮)50.0000g; 盐1.0000g; 葱花1.0000g' },
+        { id: 2, name: '炝炒西葫芦',categoryId:12,categoryName:"软食", ingredients: '盐1.0000g; 菜籽油5.0000ml; 西葫芦200.0000g' },
+        { id: 3, name: '胡萝卜肉饼',categoryId:13,categoryName:"半流食", ingredients: '胡萝卜(红)50.0000g; 猪肉(肥、瘦)150.0000g' },
+        { id: 4, name: '肉饼', categoryId:11,categoryName:"普食",ingredients: '猪肉(肥、瘦)150.0000g; 盐1.0000g; 葱花2.0000g' },
+        { id: 5, name: '腌白菜',categoryId:12,categoryName:"软食", ingredients: '白菜(大白菜)150.0000g; 冰糖20.0000g' },
+        { id: 6, name: '珍珠圆子', categoryId:11,categoryName:"普食",ingredients: '盐2.0000g; 酱油(味精)1.0000g; 淀粉50.0000g' },
+        { id: 7, name: '豆腐虾仁汤',categoryId:13,categoryName:"半流食", ingredients: '豆腐250.0000g; 明虾50.0000g; 白菜100.0000g' },
+        { id: 8, name: '番茄金针烩肉末',categoryId:11,categoryName:"普食", ingredients: '番茄(西红柿)50.0000g; 金针菇100.0000g' },
+        { id: 9, name: '莴笋丝',categoryId:12,categoryName:"软食", ingredients: '莴笋笋(莴苣)100.0000g; 盐1.0000g; 油3.0000ml' },
+        { id: 10, name: '清炒菜心',categoryId:11,categoryName:"普食", ingredients: '菜心200.0000g; 蒜末5.0000g; 盐1.0000g' }
+    ]);
+
+    // 食谱相关方法
+    const selectCategory = (categoryId: string) => {
+        currentCategory.value = categoryId;
+        // TODO: 根据分类加载食谱列表
+    };
+
+    const handleSelectionChange = (selection: Recipe[]) => {
+        selectedRecipes.value = selection;
+    };
+
+    const removeSelectedRecipe = (recipe: Recipe) => {
+        selectedRecipes.value = selectedRecipes.value.filter((item) => item.id !== recipe.id);
+        multipleTable.value ?.toggleRowSelection(recipe, false);
+    };
+
+    // 食材相关方法
+    const handleNodeClick = (data: any) => {
+        if (!data.children) {
+            // 如果是叶子节点,加载对应分类的食材列表
+            console.log('加载分类:', data.label);
+        }
+    };
+
+    // 处理食材选择变化
+    const handleFoodSelectionChange = (selection: Food[]) => {
+        selectedFoods.value = selection.map((item) => ({
+            name: item.name
+        }));
+    };
+
+    // 移除标签
+    const handleRemoveTag = (item: Food) => {
+        selectedFoods.value = selectedFoods.value.filter((food) => food.name !== item.name);
+        const foodToUnselect = foodList.value.find((food) => food.name === item.name);
+        if (foodToUnselect) {
+            foodTableRef.value ?.toggleRowSelection(foodToUnselect, false);
+        }
+    };
+
+    const tableRowClassName = ({ row }: { row: any }) => {
+        return selectedFoods.value.some((item) => item.name === row.name) ? 'selected-row' : '';
+    };
+
+    // 通用方法
+    const handleSizeChange = (val: number) => {
+        pageSize.value = val;
+        // TODO: 重新加载数据
+    };
+
+    const handleCurrentChange = (val: number) => {
+        currentPage.value = val;
+        // TODO: 重新加载数据
+    };
+
+    // 打开弹窗方法
+    const openDialog = (type: 'recipe' | 'food', initialData ? : Recipe[] | Food[]) => {
+        dialogVisible.value = true;
+        dialogType.value = type;
+
+        if (type === 'recipe') {
+            selectedRecipes.value = [];
+            if (initialData) {
+                selectedRecipes.value = [...(initialData as Recipe[])];
+                nextTick(() => {
+                    (initialData as Recipe[]).forEach((recipe) => {
+                        const tableRecipe = recipeList.value.find((r) => r.id === recipe.id);
+                        if (tableRecipe) {
+                            multipleTable.value ?.toggleRowSelection(tableRecipe, true);
+                        }
+                    });
+                });
+            }
+        } else if (type === 'food') {
+            if (initialData && Array.isArray(initialData)) {
+                // 确保初始数据是数组
+                const initialFoods = initialData as Food[];
+                selectedFoods.value = [...initialFoods];
+
+                // 等待表格渲染完成后设置选中状态
+                nextTick(() => {
+                    // 先清除所有选中状态
+                    foodTableRef.value ?.clearSelection();
+
+                    // 遍历表格数据,找到匹配的项并设置选中
+                    foodList.value.forEach((food) => {
+                        const shouldSelect = initialFoods.some((selected) => selected.name === food.name);
+                        if (shouldSelect) {
+                            foodTableRef.value ?.toggleRowSelection(food, true);
+                        }
+                    });
+                });
+            } else {
+                selectedFoods.value = [];
+                nextTick(() => {
+                    foodTableRef.value ?.clearSelection();
+                });
+            }
+        }
+    };
+
+    // 关闭弹窗方法
+    const closeDialog = () => {
+        dialogVisible.value = false;
+        selectedRecipes.value = [];
+        selectedFoods.value = [];
+    };
+
+    // 确认选择
+    const handleConfirm = () => {
+        const result = dialogType.value === 'recipe' ? selectedRecipes.value : selectedFoods.value;
+        emit('confirm', result);
+        closeDialog();
+    };
+
+    // 取消选择
+    const handleCancel = () => {
+        emit('cancel');
+        closeDialog();
+    };
+
+    // 暴露方法给父组件
+    defineExpose({
+        openDialog,
+        closeDialog
+    });
+</script>
+
+<style lang="scss" scoped>
+    :deep(.el-dialog) {
+        .el-dialog__header {
+            margin: 0;
+            padding: 20px 20px 0;
+
+            .el-dialog__title {
+                font-size: 16px;
+                font-weight: 600;
+                color: #303133;
+            }
+        }
+
+        .el-dialog__body {
+            padding: 20px;
+        }
+
+        .el-dialog__headerbtn {
+            top: 20px;
+        }
+    }
+
+    .dialog-body {
+        .recipe-search {
+            margin-bottom: 20px;
+
+            .search-wrapper {
+                display: flex;
+                gap: 12px;
+                align-items: center;
+
+                .search-input {
+                    flex: 1;
+
+                    :deep(.el-input__wrapper) {
+                        box-shadow: 0 0 0 1px #dcdfe6 inset;
+
+                        &:hover {
+                            box-shadow: 0 0 0 1px #c0c4cc inset;
+                        }
+
+                        &.is-focus {
+                            box-shadow: 0 0 0 1px #409eff inset;
+                        }
+                    }
+
+                    :deep(.el-input__prefix) {
+                        color: #909399;
+                    }
+                }
+
+                .search-btn {
+                    min-width: 80px;
+
+                    .el-icon {
+                        margin-right: 4px;
+                    }
+                }
+            }
+        }
+
+        .recipe-content {
+            display: flex;
+            gap: 12px;
+            height: 480px;
+            border: 1px solid #e4e7ed;
+            border-radius: 4px;
+
+            .recipe-categories {
+                width: 240px;
+                flex: 0 0 240px;
+                border-right: 1px solid #e4e7ed;
+                overflow-y: auto;
+
+                .category-item {
+                    padding: 12px 20px;
+                    cursor: pointer;
+                    color: #606266;
+                    font-size: 14px;
+
+                    &:hover {
+                        background-color: #f5f7fa;
+                    }
+
+                    &.active {
+                        background-color: #f0f7ff;
+                        color: #409eff;
+                    }
+                }
+            }
+
+            .recipe-list {
+                flex: 1;
+                display: flex;
+                flex-direction: column;
+                overflow: hidden;
+                background-color: #fff;
+
+                :deep(.el-table) {
+                    flex: 1;
+                    overflow-y: auto;
+
+                    .el-table__header {
+                        th {
+                            background-color: #f5f7fa;
+                            color: #606266;
+                            font-weight: 500;
+                            padding: 8px 0;
+                        }
+                    }
+
+                    .el-table__body {
+                        td {
+                            padding: 8px 0;
+                        }
+                    }
+
+                    .cell {
+                        color: #606266;
+                    }
+                }
+
+                .pagination {
+                    padding: 12px 20px;
+                    display: flex;
+                    justify-content: flex-end;
+                    background-color: #fff;
+                    border-top: 1px solid #e4e7ed;
+                }
+            }
+        }
+
+        .selected-recipes {
+            margin-top: 16px;
+            padding: 12px 16px;
+            border-top: 1px solid #e4e7ed;
+            color: #606266;
+            font-size: 14px;
+            min-height: 44px;
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+            align-items: center;
+        }
+    }
+
+    .food-dialog-content {
+        .search-bar {
+            margin-bottom: 16px;
+            display: flex;
+            align-items: center;
+            gap: 12px;
+
+            .search-input {
+                flex: 1;
+                min-width: 0;
+            }
+
+            .search-btn {
+                flex-shrink: 0;
+                min-width: 80px;
+            }
+        }
+
+        .food-select-container {
+            display: flex;
+            gap: 16px;
+            min-height: 400px;
+            margin-bottom: 16px;
+
+            .food-categories {
+                width: 200px;
+                border: 1px solid #e4e7ed;
+                border-radius: 4px;
+                overflow-y: auto;
+            }
+
+            .food-list {
+                flex: 1;
+                display: flex;
+                flex-direction: column;
+
+                :deep(.el-table) {
+                    flex: 1;
+
+                    .selected-row {
+                        background-color: #f0f7ff;
+                    }
+                }
+
+                .pagination {
+                    margin-top: 16px;
+                    display: flex;
+                    justify-content: flex-end;
+                }
+            }
+        }
+
+        .selected-tags {
+            border-top: 1px solid #e4e7ed;
+            padding-top: 16px;
+            min-height: 32px;
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            flex-wrap: wrap;
+
+            .selected-title {
+                color: #606266;
+                margin-bottom: 0;
+                margin-right: 8px;
+                white-space: nowrap;
+            }
+
+            .tags-container {
+                display: flex;
+                flex-wrap: wrap;
+                gap: 8px;
+                align-items: center;
+
+                .selected-tag {
+                    margin-right: 0;
+                    margin-bottom: 0;
+                }
+            }
+        }
+    }
+
+    .dialog-footer {
+        display: flex;
+        justify-content: center;
+        gap: 12px;
+
+        .el-button {
+            min-width: 100px;
+        }
+    }
+</style>

+ 628 - 0
src/views/patients/dietTherapy/components/RecipeTemplate.vue

@@ -0,0 +1,628 @@
+<template>
+  <el-dialog
+    v-model="dialogVisible"
+    title="添加食谱"
+    width="1100px"
+    :close-on-click-modal="false"
+    :close-on-press-escape="false"
+    :show-close="true"
+    append-to-body
+  >
+    <div class="recipe-template-dialog">
+      <!-- 顶部tab和搜索 -->
+      <div class="header-bar">
+        <el-tabs v-model="activeTab" class="tabs">
+          <el-tab-pane label="日食谱" name="day"></el-tab-pane>
+          <el-tab-pane label="周食谱" name="week"></el-tab-pane>
+        </el-tabs>
+        <div class="search-bar">
+          <el-input v-model="search" placeholder="请输入食谱名称" class="search-input">
+            <template #prefix>
+              <el-icon><Search /></el-icon>
+            </template>
+          </el-input>
+          <el-button type="primary" class="search-btn">查询</el-button>
+        </div>
+      </div>
+      <div class="main-content">
+        <!-- 左侧树 -->
+        <div class="left-panel">
+          <el-scrollbar>
+            <el-tree
+              ref="treeRef"
+              :data="treeData"
+              :show-checkbox="activeTab === 'day'"
+              node-key="id"
+              :default-expand-all="true"
+              :props="treeProps"
+              :highlight-current="activeTab === 'week'"
+              :expand-on-click-node="activeTab === 'week'"
+              @check="updateSelectedLeafNodes"
+              @node-click="handleNodeClick"
+              class="tree-block"
+            />
+          </el-scrollbar>
+        </div>
+        <!-- 右侧分组明细表格 -->
+        <div class="right-content">
+          <!-- 周食谱顶部tab -->
+          <div v-if="activeTab === 'week'" class="week-tabs">
+            <el-tabs v-model="activeWeekTab" class="recipe-tabs" @tab-change="handleWeekTabChange">
+              <el-tab-pane v-for="tab in weekTabs" :key="tab" :label="tab" :name="tab"></el-tab-pane>
+            </el-tabs>
+          </div>
+          <el-scrollbar>
+            <el-table
+              v-if="groupedDetail.length"
+              :data="groupedDetail"
+              class="detail-table"
+              :row-class-name="groupRowClassName"
+              :span-method="groupSpanMethod"
+              :border="false"
+              :show-header="false"
+              size="small"
+              style="width: 580px"
+            >
+              <el-table-column prop="meal" label="餐次" min-width="200">
+                <template #default="{row}">
+                  <div v-if="row._isGroup" class="group-header">
+                    <span class="meal-name">{{ row.meal }}</span>
+                    <span class="total-kcal">{{ row.kcalSum.toFixed(4) }}kcal</span>
+                  </div>
+                  <span v-else>{{ row.meal }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column prop="weight" label="克" width="160" align="right">
+                <template #default="{row}">
+                  {{ row._isGroup ? '' : row.weight }}
+                </template>
+              </el-table-column>
+              <el-table-column prop="kcal" label="千卡" width="160" align="right">
+                <template #default="{row}">
+                  {{ row._isGroup ? '' : row.kcal }}
+                </template>
+              </el-table-column>
+            </el-table>
+            <div v-else class="empty-data">暂无数据</div>
+          </el-scrollbar>
+        </div>
+      </div>
+      <!-- 底部已选 -->
+      <div class="selected-bar">
+        <span>已选({{ selected.length }}):</span>
+        <div class="selected-tags">
+          <el-tag v-for="item in selected" :key="item.id" closable @close="removeSelected(item)" size="small">{{ item.label }}</el-tag>
+        </div>
+      </div>
+    </div>
+    <template #footer>
+      <div class="footer-btns">
+        <el-button @click="closeDialog">取消</el-button>
+        <el-button type="primary" @click="confirmDialog">确定</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import {ref, defineExpose, nextTick, computed} from 'vue';
+import {Search} from '@element-plus/icons-vue';
+import {ElMessage} from 'element-plus';
+
+interface RecipeDetail {
+  meal: string;
+  dish: string;
+  weight: string;
+  kcal: string;
+}
+
+interface Recipe {
+  label: string;
+  detail?: RecipeDetail[];
+}
+
+const emit = defineEmits<{
+  (
+    e: 'confirm',
+    data: Array<{
+      name: string;
+      meals: Array<{
+        meal: string;
+        dish: string;
+        weight: string;
+        kcal: string;
+      }>;
+    }>
+  ): void;
+  (e: 'cancel'): void;
+}>();
+
+const dialogVisible = ref(false);
+const activeTab = ref('day');
+const search = ref('');
+const activeWeekTab = ref('食谱一');
+const weekTabs = ['食谱一', '食谱二', '食谱三', '食谱四', '食谱五', '食谱六', '食谱七'];
+
+// 当前选中的节点
+const currentNode = ref<any>(null);
+
+// 模拟周食谱数据
+const weekRecipeDetail = {
+  '东北地区夏季糖尿病食谱': {
+    '食谱一': [
+      {meal: '早餐', dish: '蒸南瓜(大份)', weight: '200.0000g', kcal: '37.4000kcal'},
+      {meal: '早餐', dish: '纯牛奶', weight: '250.0000g', kcal: '125.0000kcal'},
+      {meal: '早餐', dish: '皮蛋豆腐', weight: '100.0000g', kcal: '116.5950kcal'},
+      {meal: '中餐', dish: '鸡汤面', weight: '130.0000g', kcal: '374.5000kcal'},
+      {meal: '中餐', dish: '豌豆胡萝卜', weight: '70.0000g', kcal: '163.6040kcal'},
+      {meal: '中餐', dish: '蘑菇青菜炒肉', weight: '85.0000g', kcal: '88.0150kcal'},
+      {meal: '中加', dish: '水果', weight: '150.0000g', kcal: '36.8000kcal'}
+    ],
+    '食谱二': [
+      {meal: '早餐', dish: '小米粥', weight: '200.0000g', kcal: '180.0000kcal'},
+      {meal: '早餐', dish: '煮鸡蛋', weight: '50.0000g', kcal: '77.0000kcal'},
+      {meal: '早餐', dish: '生菜', weight: '100.0000g', kcal: '15.0000kcal'},
+      {meal: '中餐', dish: '糙米饭', weight: '150.0000g', kcal: '165.0000kcal'},
+      {meal: '中餐', dish: '清炒小白菜', weight: '200.0000g', kcal: '42.0000kcal'},
+      {meal: '中餐', dish: '红烧带鱼', weight: '150.0000g', kcal: '195.0000kcal'},
+      {meal: '中加', dish: '酸奶', weight: '200.0000g', kcal: '120.0000kcal'}
+    ]
+  }
+};
+
+// 处理树节点点击
+const handleNodeClick = (node: any) => {
+  if (activeTab.value === 'week') {
+    currentNode.value = node;
+    // 如果是叶子节点,更新右侧表格数据
+    if (!node.children || !node.children.length) {
+      const recipeData = weekRecipeDetail[node.label]?.[activeWeekTab.value] || [];
+      selectedDetail.value = recipeData;
+    }
+  }
+};
+
+// 监听周食谱tab切换
+const handleWeekTabChange = (tab: string) => {
+  if (currentNode.value && (!currentNode.value.children || !currentNode.value.children.length)) {
+    const recipeData = weekRecipeDetail[currentNode.value.label]?.[tab] || [];
+    selectedDetail.value = recipeData;
+  }
+};
+
+// 更新树形数据结构
+const treeData = ref([
+  {
+    id: 1,
+    label: '基础公共模版',
+    children: [
+      {
+        id: 2,
+        label: '糖尿病周食谱(东北地区)分类',
+        children: [
+          {
+            id: 3,
+            label: '东北地区冬季糖尿病食谱',
+            detail: [
+              {meal: '早餐', dish: '蒸芋头', weight: '50.0000g', kcal: '33.1800kcal'},
+              {meal: '早餐', dish: '豆腐脑', weight: '200.0000g', kcal: '20.0000kcal'},
+              {meal: '早餐', dish: '西兰花木耳', weight: '105.0000g', kcal: '72.6650kcal'},
+              {meal: '中餐', dish: '杂粮饭', weight: '60.0000g', kcal: '203.0000kcal'},
+              {meal: '中餐', dish: '尖椒牛肉', weight: '100.0000g', kcal: '146.2800kcal'},
+              {meal: '中餐', dish: '老黄瓜羊肉汤', weight: '240.0000g', kcal: '78.7800kcal'},
+              {meal: '中餐', dish: '蕾蒿菜', weight: '50.0000g', kcal: '7.6960kcal'}
+            ]
+          },
+          {
+            id: 4,
+            label: '东北地区夏季糖尿病食谱',
+            detail: [
+              // ... 其他食谱数据 ...
+            ]
+          },
+          {
+            id: 5,
+            label: '东北地区春季糖尿病食谱',
+            detail: [
+              // ... 其他食谱数据 ...
+            ]
+          },
+          {
+            id: 6,
+            label: '东北地区秋季糖尿病食谱',
+            detail: [
+              // ... 其他食谱数据 ...
+            ]
+          }
+        ]
+      },
+      {
+        id: 7,
+        label: '减脂周食谱分类',
+        children: [
+          {
+            id: 8,
+            label: '中速食谱方案',
+            detail: [
+              // ... 其他食谱数据 ...
+            ]
+          },
+          {
+            id: 9,
+            label: '低速食谱方案',
+            detail: [
+              // ... 其他食谱数据 ...
+            ]
+          }
+        ]
+      },
+      {
+        id: 10,
+        label: '普食周食谱分类',
+        children: [
+          {
+            id: 11,
+            label: '低碳周食谱一',
+            detail: [
+              // ... 其他食谱数据 ...
+            ]
+          },
+          {
+            id: 12,
+            label: '低蛋白周食谱一',
+            detail: [
+              // ... 其他食谱数据 ...
+            ]
+          },
+          {
+            id: 13,
+            label: '无渣(少渣)食谱一',
+            detail: [
+              // ... 其他食谱数据 ...
+            ]
+          }
+        ]
+      }
+    ]
+  }
+]);
+const treeProps = {children: 'children', label: 'label'};
+const treeRef = ref();
+
+const selected = ref<any[]>([]);
+const selectedDetail = ref<any[]>([]);
+
+// 分组明细数据
+const groupedDetail = computed(() => {
+  if (activeTab.value === 'week') {
+    const all = selectedDetail.value;
+    if (!all.length) return [];
+
+    // 按meal分组
+    const groupMap: Record<string, {meal: string; kcalSum: number; rows: any[]}> = {};
+    all.forEach((row) => {
+      if (!groupMap[row.meal]) {
+        groupMap[row.meal] = {meal: row.meal, kcalSum: 0, rows: []};
+      }
+      const kcal = parseFloat((row.kcal || '').replace(/[^\d.]/g, '')) || 0;
+      groupMap[row.meal].kcalSum += kcal;
+      groupMap[row.meal].rows.push(row);
+    });
+
+    // 展平成表格数据,插入分组行
+    const result: any[] = [];
+    Object.values(groupMap).forEach((group) => {
+      result.push({_isGroup: true, meal: `${group.meal}`, kcalSum: group.kcalSum});
+      result.push(...group.rows);
+    });
+    return result;
+  } else {
+    // 原有的日食谱数据处理逻辑
+    const all = selectedDetail.value;
+    if (!all.length) return [];
+    const groupMap: Record<string, {meal: string; kcalSum: number; rows: any[]}> = {};
+    all.forEach((row) => {
+      if (!groupMap[row.meal]) {
+        groupMap[row.meal] = {meal: row.meal, kcalSum: 0, rows: []};
+      }
+      const kcal = parseFloat((row.kcal || '').replace(/[^\d.]/g, '')) || 0;
+      groupMap[row.meal].kcalSum += kcal;
+      groupMap[row.meal].rows.push(row);
+    });
+    const result: any[] = [];
+    Object.values(groupMap).forEach((group) => {
+      result.push({_isGroup: true, meal: group.meal, kcalSum: group.kcalSum});
+      result.push(...group.rows);
+    });
+    return result;
+  }
+});
+
+function groupRowClassName({row}: any) {
+  return row._isGroup ? 'group-row' : '';
+}
+function groupSpanMethod({row, column, rowIndex, columnIndex}: any) {
+  if (row._isGroup) {
+    if (columnIndex === 0) return [1, 4];
+    return [0, 0];
+  }
+}
+
+function updateSelectedLeafNodes() {
+  const checkedNodes = treeRef.value?.getCheckedNodes(true) || [];
+  selected.value = checkedNodes.filter((node: any) => !node.children || node.children.length === 0);
+  selectedDetail.value = selected.value.flatMap((tpl) => tpl.detail || []);
+}
+function removeSelected(item: any) {
+  treeRef.value?.setChecked(item, false, true);
+  updateSelectedLeafNodes();
+}
+function openDialog() {
+  dialogVisible.value = true;
+  nextTick(() => updateSelectedLeafNodes());
+}
+function closeDialog() {
+  dialogVisible.value = false;
+}
+function confirmDialog() {
+  if (!selected.value.length) {
+    ElMessage.warning('请选择食谱');
+    return;
+  }
+
+  // 获取选中食谱的详细数据
+  const selectedRecipeData = selected.value.map((recipe) => {
+    const detail = recipe.detail || [];
+    return {
+      name: recipe.label,
+      meals: detail.map((item) => ({
+        meal: item.meal,
+        dish: item.dish,
+        weight: item.weight,
+        kcal: item.kcal
+      }))
+    };
+  });
+
+  emit('confirm', selectedRecipeData);
+  dialogVisible.value = false;
+  selected.value = [];
+  selectedDetail.value = [];
+}
+defineExpose({openDialog, closeDialog});
+</script>
+
+<style lang="scss" scoped>
+.recipe-template-dialog {
+  background: #fff;
+  display: flex;
+  flex-direction: column;
+  height: 600px;
+
+  .header-bar {
+    padding: 0 20px;
+    .tabs {
+      :deep(.el-tabs__nav) {
+        width: 100%;
+        display: flex;
+        .el-tabs__item {
+          flex: 1;
+          text-align: center;
+          font-size: 15px;
+          height: 40px;
+          line-height: 40px;
+          &.is-active {
+            font-weight: 500;
+          }
+        }
+      }
+    }
+    .search-bar {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+      padding: 12px 0;
+      .search-input {
+        flex: 1;
+        :deep(.el-input__wrapper) {
+          box-shadow: 0 0 0 1px #dcdfe6 inset;
+          &:hover {
+            box-shadow: 0 0 0 1px #c0c4cc inset;
+          }
+        }
+      }
+      .search-btn {
+        min-width: 80px;
+      }
+    }
+  }
+
+  .main-content {
+    display: flex;
+    flex: 1;
+    padding: 0 20px;
+    min-height: 400px;
+    gap: 20px;
+    overflow: hidden;
+
+    .left-panel {
+      width: 360px;
+      border-right: 1px solid #ebeef5;
+      padding-right: 20px;
+      .tree-block {
+        :deep(.el-tree-node__content) {
+          height: 32px;
+          &:hover {
+            background-color: #f5f7fa;
+          }
+          &.is-current {
+            background-color: #ecf5ff;
+            color: #409eff;
+          }
+        }
+        :deep(.el-tree-node__children) {
+          padding-left: 22px;
+        }
+        :deep(.el-checkbox__inner) {
+          border-radius: 2px;
+          &:hover {
+            border-color: #409eff;
+          }
+        }
+        :deep(.el-tree-node__expand-icon) {
+          &.expanded {
+            transform: rotate(90deg);
+          }
+          &.is-leaf {
+            color: transparent;
+          }
+        }
+      }
+    }
+
+    .right-content {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+
+      .week-tabs {
+        margin-bottom: 16px;
+        .recipe-tabs {
+          :deep(.el-tabs__nav) {
+            background-color: #f5f7fa;
+            padding: 4px;
+            border-radius: 4px;
+            .el-tabs__item {
+              padding: 0 20px;
+              height: 32px;
+              line-height: 32px;
+              border-radius: 4px;
+              &.is-active {
+                background-color: #fff;
+                box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+              }
+            }
+          }
+        }
+      }
+
+      .detail-table {
+        :deep(.el-table__inner-wrapper) {
+          &::before {
+            display: none;
+          }
+        }
+        :deep(.group-row) {
+          background: #f5f7fa;
+          td {
+            background: #f5f7fa !important;
+            padding: 8px 0;
+            border-bottom: 1px solid #ebeef5;
+
+            .group-header {
+              display: flex;
+              align-items: center;
+              justify-content: space-between;
+              padding: 0 20px;
+              height: 40px;
+
+              .meal-name {
+                font-size: 14px;
+                font-weight: 500;
+                color: #333;
+              }
+
+              .total-kcal {
+                font-size: 14px;
+                color: #333;
+              }
+            }
+          }
+        }
+        :deep(.content-row) {
+          td {
+            height: 40px;
+            padding: 8px 20px;
+            color: #606266;
+            font-size: 14px;
+            border-bottom: 1px solid #ebeef5;
+            background: #fff !important;
+          }
+          &:hover > td {
+            background-color: #f5f7fa !important;
+          }
+        }
+        :deep(.el-table__row) {
+          td {
+            transition: background-color 0.25s ease;
+          }
+        }
+      }
+    }
+  }
+
+  .selected-bar {
+    flex: none;
+    min-height: 32px;
+    border-top: 1px solid #ebeef5;
+    margin-top: 12px;
+    padding: 12px 20px;
+    display: flex;
+    align-items: flex-start;
+    gap: 8px;
+    > span {
+      color: #606266;
+      line-height: 24px;
+    }
+    .selected-tags {
+      flex: 1;
+      display: flex;
+      flex-wrap: wrap;
+      gap: 8px;
+      :deep(.el-tag) {
+        margin: 0;
+        border-radius: 2px;
+        .el-tag__close {
+          color: #909399;
+          &:hover {
+            color: #409eff;
+            background-color: transparent;
+          }
+        }
+      }
+    }
+  }
+
+  :deep(.el-scrollbar) {
+    height: 100%;
+    .el-scrollbar__wrap {
+      overflow-x: hidden;
+    }
+    .el-scrollbar__bar {
+      &.is-horizontal {
+        display: none;
+      }
+    }
+  }
+}
+
+:deep(.el-dialog) {
+  margin-top: 8vh !important;
+  .el-dialog__header {
+    margin: 0;
+    padding: 16px 20px;
+    border-bottom: 1px solid #ebeef5;
+    .el-dialog__title {
+      font-size: 16px;
+      font-weight: 500;
+    }
+  }
+  .el-dialog__body {
+    padding: 0;
+  }
+  .el-dialog__footer {
+    border-top: 1px solid #ebeef5;
+    padding: 12px 20px;
+  }
+}
+</style>

+ 256 - 0
src/views/patients/dietTherapy/dailyIndex.vue

@@ -0,0 +1,256 @@
+<template>
+    <div class="p-2">
+        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+            <div v-show="showSearch" class="mb-[10px]">
+                <el-card shadow="hover">
+                    <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
+                        <el-form-item label="推荐时间:">
+                            <el-date-picker v-model="queryParams.dateRange" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
+                        </el-form-item>
+                        <el-form-item label="看诊类型:">
+                            <el-select v-model="queryParams.type" class="spec-unit-select">
+                                <el-option v-for="dict in treatment_user_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+                            </el-select>
+                        </el-form-item>
+                        <el-form-item>
+                            <el-input v-model="queryParams.searchValue" placeholder="门诊号/住院号/医生" style="width: 240px; " clearable />
+                        </el-form-item>
+                        <el-form-item>
+                            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                        </el-form-item>
+                    </el-form>
+                </el-card>
+            </div>
+        </transition>
+
+        <el-card shadow="never">
+            <template #header>
+                <el-row :gutter="10" class="mb8">
+                    <el-col :span="1.5">
+                        <el-button type="primary" plain icon="Plus" @click="emit('addDaily')" v-hasPermi="['patients:dailyMealPlan:add']">新增日常膳食</el-button>
+                    </el-col>
+                    <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+                </el-row>
+            </template>
+
+            <el-table v-loading="loading" border :data="mealPlanList" @selection-change="handleSelectionChange">
+                <el-table-column type="selection" width="55" align="center" />
+                <el-table-column label="推荐时间" align="center" prop="createTime" />
+                <el-table-column label="看诊类型" align="center" prop="type">
+                    <template #default="scope">
+                        <span>{{getDictLabel(treatment_user_type ,scope.row.type )|| '--' }}</span>
+                    </template>
+                </el-table-column>
+                <el-table-column label="门诊/住院号" align="center" prop="outpatientNo" />
+                <el-table-column label="推荐医生" align="center" prop="createByUser" />
+                <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                    <template #default="scope">
+                        <el-tooltip content="修改" placement="top">
+                            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['patients:dailyMealPlan:edit']"></el-button>
+                        </el-tooltip>
+                        <el-tooltip content="删除" placement="top">
+                            <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['patients:dailyMealPlan:remove']"></el-button>
+                        </el-tooltip>
+                    </template>
+                </el-table-column>
+            </el-table>
+
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+        </el-card>
+
+    </div>
+</template>
+
+<script setup name="MealPlan" lang="ts">
+    import { listDailyMealPlan, getMealPlan, delMealPlan, addMealPlan, updateMealPlan } from '@/api/patients/dailyMealPlan';
+    import { MealPlanVO, MealPlanQuery, MealPlanForm } from '@/api/patients/dailyMealPlan/types';
+
+    const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+    const { treatment_user_type } = toRefs < any > (proxy ?.useDict('treatment_user_type'));
+
+    const mealPlanList = ref < MealPlanVO[] > ([]);
+    const buttonLoading = ref(false);
+    const loading = ref(true);
+    const showSearch = ref(true);
+    const ids = ref < Array < string | number >> ([]);
+    const single = ref(true);
+    const multiple = ref(true);
+    const total = ref(0);
+    const emit = defineEmits(['addDaily']);
+
+    const queryFormRef = ref < ElFormInstance > ();
+    const mealPlanFormRef = ref < ElFormInstance > ();
+
+    const dialog = reactive < DialogOption > ({
+        visible: false,
+        title: ''
+    });
+    const props = defineProps({
+        patientInfo: {
+            type: Object,
+            required: true,
+            default: () => ({
+                id: '',
+                name: '',
+                age: '',
+                gender: ''
+            })
+        }
+    });
+
+    const initFormData: MealPlanForm = {
+        id: undefined,
+        type: props.patientInfo.type,
+        patientId: props.patientInfo.id,
+        outpatientNo: props.patientInfo.outpatientNo,
+        deptId: props.patientInfo.deptId,
+        recommendStartDate: undefined,
+        recommendEndDate: undefined,
+        status: undefined,
+        remark: undefined,
+        mealRecipeList: [],
+    }
+    const data = reactive < PageData < MealPlanForm,
+        MealPlanQuery >> ({
+            form: { ...initFormData },
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+                type: undefined,
+                patientId: props.patientInfo.id,
+                outpatientNo: undefined,
+                deptId: undefined,
+                dateRange: [],
+                searchValue: undefined,
+                params: {}
+            },
+            rules: {
+                id: [
+                    { required: true, message: "主键不能为空", trigger: "blur" }
+                ],
+                patientId: [
+                    { required: true, message: "患者ID不能为空", trigger: "blur" }
+                ],
+            }
+        });
+
+    const { queryParams, form, rules } = toRefs(data);
+
+    /** 查询日常膳食主列表 */
+    const getList = async () => {
+        loading.value = true;
+        const res = await listDailyMealPlan(queryParams.value);
+        mealPlanList.value = res.rows;
+        total.value = res.total;
+        loading.value = false;
+    }
+
+    // 字典label工具
+    function getDictLabel(dictList: any[], value: string) {
+        if (!dictList || !Array.isArray(dictList)) return value || '--';
+        const found = dictList.find(item => item.value === value);
+        return found ? found.label : value || '--';
+    }
+
+    /** 取消按钮 */
+    const cancel = () => {
+        reset();
+        dialog.visible = false;
+    }
+
+    /** 表单重置 */
+    const reset = () => {
+        form.value = { ...initFormData };
+        mealPlanFormRef.value ?.resetFields();
+        queryParams.value.dateRange = undefined;
+        queryParams.value.type = undefined;
+        queryParams.value.searchValue = undefined;
+        queryParams.value.pageNum = 1;
+        queryParams.value.pageSize = 10;
+    }
+
+    /** 搜索按钮操作 */
+    const handleQuery = () => {
+        queryParams.value.pageNum = 1;
+        getList();
+    }
+
+    /** 重置按钮操作 */
+    const resetQuery = () => {
+        queryFormRef.value ?.resetFields();
+        queryParams.value.dateRange = undefined;
+        queryParams.value.type = undefined;
+        queryParams.value.searchValue = undefined;
+        queryParams.value.pageNum = 1;
+        queryParams.value.pageSize = 10;
+        handleQuery();
+    }
+
+    /** 多选框选中数据 */
+    const handleSelectionChange = (selection: MealPlanVO[]) => {
+        ids.value = selection.map(item => item.id);
+        single.value = selection.length != 1;
+        multiple.value = !selection.length;
+    }
+
+    /** 新增按钮操作 */
+    const handleAdd = () => {
+        reset();
+        dialog.visible = true;
+        dialog.title = "添加日常膳食";
+    }
+
+    /** 修改按钮操作 */
+    const handleUpdate = async (row ? : MealPlanVO) => {
+        reset();
+        const _id = row ?.id || ids.value[0]
+        const res = await getMealPlan(_id);
+        Object.assign(form.value, res.data);
+        dialog.visible = true;
+        dialog.title = "修改日常膳食";
+    }
+
+    /** 提交按钮 */
+    const submitForm = (mealPlanForm) => {
+
+        console.log('------' + mealPlanForm);
+
+        form.value.recommendStartDate = mealPlanForm.mealTimeRange[0];
+        form.value.recommendEndDate = mealPlanForm.mealTimeRange[1];
+        form.value.mealRecipeList = mealPlanForm.mealRecipeList;
+        addMealPlan(form.value).finally(() => buttonLoading.value = false);
+        getList();
+        mealPlanFormRef.value ?.validate(async (valid: boolean) => {
+            if (true) {
+                form.value.recommendStartDate = mealPlanForm.mealTimeRange[0];
+                form.value.recommendEndDate = mealPlanForm.mealTimeRange[1];
+                form.value.mealRecipeList = mealPlanForm.mealRecipeList;
+                buttonLoading.value = true;
+                if (form.value.id) {
+                    await updateMealPlan(form.value).finally(() => buttonLoading.value = false);
+                } else {
+                    await addMealPlan(form.value).finally(() => buttonLoading.value = false);
+                }
+                proxy ?.$modal.msgSuccess("操作成功");
+                dialog.visible = false;
+                await getList();
+            }
+        });
+    }
+
+    defineExpose({ submitForm });
+
+    /** 删除按钮操作 */
+    const handleDelete = async (row ? : MealPlanVO) => {
+        const _ids = row ?.id || ids.value;
+        await proxy ?.$modal.confirm('是否确认删除日常膳食主编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
+        await delMealPlan(_ids);
+        proxy ?.$modal.msgSuccess("删除成功");
+        await getList();
+    }
+
+    onMounted(() => {
+        getList();
+    });
+</script>

+ 258 - 0
src/views/patients/dietTherapy/hospitalIndex.vue

@@ -0,0 +1,258 @@
+<template>
+    <div class="p-2">
+        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+            <div v-show="showSearch" class="mb-[10px]">
+                <el-card shadow="hover">
+                    <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="120px">
+                        <el-form-item label="推荐时间:">
+                            <el-date-picker v-model="queryParams.dateRange" type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />
+                        </el-form-item>
+                        <el-form-item label="看诊类型:">
+                            <el-select v-model="queryParams.type" class="spec-unit-select">
+                                <el-option v-for="dict in treatment_user_type" :key="dict.value" :label="dict.label" :value="dict.value" />
+                            </el-select>
+                        </el-form-item>
+                        <el-form-item>
+                            <el-input v-model="queryParams.searchValue" placeholder="门诊号/住院号/医生" style="width: 240px; " clearable />
+                        </el-form-item>
+                        <el-form-item>
+                            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+                        </el-form-item>
+                    </el-form>
+                </el-card>
+            </div>
+        </transition>
+
+        <el-card shadow="never">
+            <template #header>
+                <el-row :gutter="10" class="mb8">
+                    <el-col :span="1.5">
+                        <el-button type="primary" plain icon="Plus" @click="emit('addHospital')" v-hasPermi="['patients:hospitalMealPlan:add']">新增院内膳食</el-button>
+                    </el-col>
+                    <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+                </el-row>
+            </template>
+
+            <el-table v-loading="loading" border :data="mealPlanList" @selection-change="handleSelectionChange">
+                <el-table-column type="selection" width="55" align="center" />
+                <el-table-column label="推荐时间" align="center" prop="createTime" />
+                <el-table-column label="看诊类型" align="center" prop="type">
+                    <template #default="scope">
+                        <span>{{getDictLabel(treatment_user_type ,scope.row.type )|| '--' }}</span>
+                    </template>
+                </el-table-column>
+                <el-table-column label="门诊/住院号" align="center" prop="outpatientNo" />
+                <el-table-column label="处方状态" align="center" prop="status">
+                    <template #default="scope">
+                        <span>{{getDictLabel(payment_status ,scope.row.status )|| '--' }}</span>
+                    </template>
+                </el-table-column>
+                <el-table-column label="推荐医生" align="center" prop="createByUser" />
+                <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                    <template #default="scope">
+                        <el-tooltip content="修改" placement="top">
+                            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['patients:hospitalMealPlan:edit']"></el-button>
+                        </el-tooltip>
+                        <el-tooltip content="删除" placement="top">
+                            <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['patients:hospitalMealPlan:remove']"></el-button>
+                        </el-tooltip>
+                    </template>
+                </el-table-column>
+            </el-table>
+
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+        </el-card>
+
+    </div>
+</template>
+
+<script setup name="MealPlan" lang="ts">
+    import { listMealPlan, getMealPlan, delMealPlan, addMealPlan, updateMealPlan } from '@/api/patients/hospitalMealPlan';
+    import { MealPlanVO, MealPlanQuery, MealPlanForm } from '@/api/patients/hospitalMealPlan/types';
+
+    const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+    const { treatment_user_type, payment_status } = toRefs < any > (proxy ?.useDict('treatment_user_type', 'payment_status'));
+
+    const mealPlanList = ref < MealPlanVO[] > ([]);
+    const buttonLoading = ref(false);
+    const loading = ref(true);
+    const showSearch = ref(true);
+    const ids = ref < Array < string | number >> ([]);
+    const single = ref(true);
+    const multiple = ref(true);
+    const total = ref(0);
+    const emit = defineEmits(['addHospital']);
+
+    const queryFormRef = ref < ElFormInstance > ();
+    const mealPlanFormRef = ref < ElFormInstance > ();
+
+    const dialog = reactive < DialogOption > ({
+        visible: false,
+        title: ''
+    });
+    const props = defineProps({
+        patientInfo: {
+            type: Object,
+            required: true,
+            default: () => ({
+                id: '',
+                name: '',
+                age: '',
+                gender: ''
+            })
+        }
+    });
+
+    const initFormData: MealPlanForm = {
+        id: undefined,
+        type: props.patientInfo.type,
+        patientId: props.patientInfo.id,
+        outpatientNo: props.patientInfo.outpatientNo,
+        deptId: props.patientInfo.deptId,
+        recommendStartDate: undefined,
+        recommendEndDate: undefined,
+        status: undefined,
+        remark: undefined,
+        mealRecipeList: [],
+    }
+    const data = reactive < PageData < MealPlanForm,
+        MealPlanQuery >> ({
+            form: { ...initFormData },
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+                type: undefined,
+                patientId: props.patientInfo.id,
+                outpatientNo: undefined,
+                deptId: undefined,
+                dateRange: [],
+                searchValue: undefined,
+                params: {}
+            },
+            rules: {
+                id: [
+                    { required: true, message: "主键不能为空", trigger: "blur" }
+                ],
+                patientId: [
+                    { required: true, message: "患者ID不能为空", trigger: "blur" }
+                ],
+            }
+        });
+
+    const { queryParams, form, rules } = toRefs(data);
+
+    /** 查询院内膳食主列表 */
+    const getList = async () => {
+        loading.value = true;
+        const res = await listMealPlan(queryParams.value);
+        mealPlanList.value = res.rows;
+        total.value = res.total;
+        loading.value = false;
+    }
+
+    // 字典label工具
+    function getDictLabel(dictList: any[], value: string) {
+        if (!dictList || !Array.isArray(dictList)) return value || '--';
+        const found = dictList.find(item => item.value === value);
+        return found ? found.label : value || '--';
+    }
+
+    /** 取消按钮 */
+    const cancel = () => {
+        reset();
+        dialog.visible = false;
+    }
+
+    /** 表单重置 */
+    const reset = () => {
+        form.value = { ...initFormData };
+        mealPlanFormRef.value ?.resetFields();
+        queryParams.value.dateRange = undefined;
+        queryParams.value.type = undefined;
+        queryParams.value.searchValue = undefined;
+        queryParams.value.pageNum = 1;
+        queryParams.value.pageSize = 10;
+    }
+
+    /** 搜索按钮操作 */
+    const handleQuery = () => {
+        queryParams.value.pageNum = 1;
+        getList();
+    }
+
+    /** 重置按钮操作 */
+    const resetQuery = () => {
+        queryFormRef.value ?.resetFields();
+         queryParams.value.dateRange = undefined;
+        queryParams.value.type = undefined;
+        queryParams.value.searchValue = undefined;
+        queryParams.value.pageNum = 1;
+        queryParams.value.pageSize = 10;
+        handleQuery();
+    }
+
+    /** 多选框选中数据 */
+    const handleSelectionChange = (selection: MealPlanVO[]) => {
+        ids.value = selection.map(item => item.id);
+        single.value = selection.length != 1;
+        multiple.value = !selection.length;
+    }
+
+    /** 新增按钮操作 */
+    const handleAdd = () => {
+        reset();
+        dialog.visible = true;
+        dialog.title = "添加院内膳食";
+    }
+
+    /** 修改按钮操作 */
+    const handleUpdate = async (row ? : MealPlanVO) => {
+        reset();
+        const _id = row ?.id || ids.value[0]
+        const res = await getMealPlan(_id);
+        Object.assign(form.value, res.data);
+        dialog.visible = true;
+        dialog.title = "修改院内膳食";
+    }
+
+    /** 提交按钮 */
+    const submitForm = (mealPlanForm) => {
+        form.value.recommendStartDate = mealPlanForm.mealTimeRange[0];
+        form.value.recommendEndDate = mealPlanForm.mealTimeRange[1];
+        form.value.mealRecipeList = mealPlanForm.mealRecipeList;
+        addMealPlan(form.value).finally(() => buttonLoading.value = false);
+        getList();
+        mealPlanFormRef.value ?.validate(async (valid: boolean) => {
+            if (true) {
+                form.value.recommendStartDate = mealPlanForm.mealTimeRange[0];
+                form.value.recommendEndDate = mealPlanForm.mealTimeRange[1];
+                form.value.mealRecipeList = mealPlanForm.mealRecipeList;
+                buttonLoading.value = true;
+                if (form.value.id) {
+                    await updateMealPlan(form.value).finally(() => buttonLoading.value = false);
+                } else {
+                    await addMealPlan(form.value).finally(() => buttonLoading.value = false);
+                }
+                proxy ?.$modal.msgSuccess("操作成功");
+                dialog.visible = false;
+                await getList();
+            }
+        });
+    }
+
+    defineExpose({ submitForm });
+
+    /** 删除按钮操作 */
+    const handleDelete = async (row ? : MealPlanVO) => {
+        const _ids = row ?.id || ids.value;
+        await proxy ?.$modal.confirm('是否确认删除院内膳食主编号为"' + _ids + '"的数据项?').finally(() => loading.value = false);
+        await delMealPlan(_ids);
+        proxy ?.$modal.msgSuccess("删除成功");
+        await getList();
+    }
+
+    onMounted(() => {
+        getList();
+    });
+</script>

+ 202 - 172
src/views/patients/dietTherapy/index.vue

@@ -1,182 +1,212 @@
 <template>
-  <div class="container">
-    <!-- 首页的tab列表区域 -->
-    <div v-show="type === PageType.LIST">
-      <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
-        <el-tab-pane label="院内膳食" name="hospital">
-          <div class="btn_content" @click="handleHospital()">
-            <el-button type="primary">新增{{ tabLabelMap[activeName] }}</el-button>
-          </div>
-        </el-tab-pane>
-        <el-tab-pane label="平衡膳食" name="balance">
-          <div class="btn_content" @click="handleBalance()">
-            <el-button type="primary">新增{{ tabLabelMap[activeName] }}</el-button>
-          </div>
-        </el-tab-pane>
-        <el-tab-pane label="日常膳食" name="daily">
-          <div class="btn_content" @click="handleDaily()">
-            <el-button type="primary">新增{{ tabLabelMap[activeName] }}</el-button>
-          </div>
-        </el-tab-pane>
-        <el-tab-pane label="智能推荐" name="smart">
-          <div class="btn_content" @click="handleSmartRecommend()">
-            <el-button type="primary">新增{{ tabLabelMap[activeName] }}</el-button>
-          </div>
-        </el-tab-pane>
-      </el-tabs>
-    </div>
+    <div class="container">
+        <!-- 首页的tab列表区域 -->
+        <div v-show="type === PageType.LIST">
+            <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
+                <el-tab-pane label="院内膳食" name="hospital">
+                    <HospitalIndex ref="hospitalIndexRef" :activeName="activeName" @addHospital="handleHospital" :patientInfo="props.patientInfo" />
+                </el-tab-pane>
+                <!-- <el-tab-pane label="平衡膳食" name="balance">
+                    <div class="btn_content" @click="handleBalance()">
+                        <el-button type="primary">新增{{ tabLabelMap[activeName] }}</el-button>
+                    </div>
+                </el-tab-pane> -->
+                <el-tab-pane label="日常膳食" name="daily">
+                    <DailyIndex ref="dailyIndexRef" :activeName="activeName" @addDaily="handleDaily" :patientInfo="props.patientInfo" />
+                </el-tab-pane>
+                <el-tab-pane label="智能推荐" name="smart">
+                    <!-- <div class="btn_content" @click="handleSmartRecommend()">
+                        <el-button type="primary">新增{{ tabLabelMap[activeName] }}</el-button>
+                    </div> -->
+                </el-tab-pane>
+            </el-tabs>
+        </div>
 
-    <!-- 院内膳食 -->
-    <div v-show="type === PageType.HOSPITAL">
-      <AddHospital @goBack="isType" />
-    </div>
+        <!-- 院内膳食 -->
+        <div v-show="type === PageType.HOSPITAL">
+            <AddHospital v-if="type === PageType.HOSPITAL" @goBack="isType" @submitHospital="handleHospitalSubmit" :patientInfo="props.patientInfo"/>
+        </div>
 
-    <!-- 平衡膳食 -->
-    <div v-show="type === PageType.BALANCE">
-      <AddBalance @goBack="isType" />
-    </div>
+        <!-- 平衡膳食 -->
+        <div v-show="type === PageType.BALANCE">
+            <AddBalance @goBack="isType" />
+        </div>
 
-    <!-- 日常膳食 -->
-    <div v-show="type === PageType.DAILY">
-      <AddDaily @goBack="isType" />
-    </div>
+        <!-- 日常膳食 -->
+        <div v-show="type === PageType.DAILY">
+            <AddDaily v-if="type === PageType.DAILY" @goBack="isType" @submitDaily="handleDailySubmit" :patientInfo="props.patientInfo"/>
+        </div>
 
-    <!-- 智能推荐 -->
-    <div v-show="type === PageType.SMART">
-      <AddSmartRec @goBack="isType" />
+        <!-- 智能推荐 -->
+        <div v-show="type === PageType.SMART">
+            <AddSmartRec @goBack="isType" />
+        </div>
     </div>
-  </div>
 </template>
 <script lang="ts" setup>
-import {ref} from 'vue';
-import type {TabsPaneContext} from 'element-plus';
-import AddHospital from './addHospital.vue';
-import AddBalance from './addBalance.vue';
-import AddDaily from './addDaily.vue';
-import AddSmartRec from './addSmartRec.vue';
-
-// 使用枚举定义页面类型
-enum PageType {
-  LIST = 'list',
-  HOSPITAL = 'addForm',
-  BALANCE = 'addBalance',
-  DAILY = 'addDaily',
-  SMART = 'addSmart'
-}
-
-// 当前页面类型
-const type = ref<PageType>(PageType.LIST);
-const activeName = ref('hospital');
-
-// tab标签映射
-const tabLabelMap: Record<string, string> = {
-  hospital: '院内膳食',
-  balance: '平衡膳食',
-  daily: '日常膳食',
-  smart: '智能推荐'
-};
-
-// 点击标签
-const handleClick = (tab: TabsPaneContext) => {
-  console.log(tab.paneName);
-};
-
-// 页面跳转处理
-const handlePageChange = (pageType: PageType) => {
-  type.value = pageType;
-};
-
-// 各个按钮的点击处理
-const handleHospital = () => {
-  handlePageChange(PageType.HOSPITAL);
-};
-// 平衡膳食
-const handleBalance = () => {
-  handlePageChange(PageType.BALANCE);
-};
-
-// 日常膳食
-const handleDaily = () => {
-  handlePageChange(PageType.DAILY);
-};
-
-// 智能推荐
-const handleSmartRecommend = () => {
-  handlePageChange(PageType.SMART);
-};
-
-// 返回列表页
-const isType = () => {
-  handlePageChange(PageType.LIST);
-};
+    import { ref } from 'vue';
+    import type { TabsPaneContext } from 'element-plus';
+    import AddHospital from './addHospital.vue';
+    import AddBalance from './addBalance.vue';
+    import AddDaily from './addDaily.vue';
+    import AddSmartRec from './addSmartRec.vue';
+    import HospitalIndex from './hospitalIndex.vue';
+    import DailyIndex from './dailyIndex.vue';
+    const emit = defineEmits(['change'])
+    const hospitalIndexRef = ref();
+    const dailyIndexRef = ref();
+
+
+    // 声明接收的 props
+    const props = defineProps({
+        patientInfo: {
+            type: Object,
+            required: true,
+            default: () => ({
+                id: '',
+                name: '',
+                age: '',
+                gender: ''
+            })
+        }
+    });
+    // 使用枚举定义页面类型
+    enum PageType {
+        LIST = 'list',
+            HOSPITAL = 'addForm',
+            BALANCE = 'addBalance',
+            DAILY = 'addDaily',
+            SMART = 'addSmart'
+    }
+
+    // 当前页面类型
+    const type = ref < PageType > (PageType.LIST);
+    const activeName = ref('hospital');
+
+    // tab标签映射
+    const tabLabelMap: Record < string, string > = {
+        hospital: '院内膳食',
+        balance: '平衡膳食',
+        daily: '日常膳食',
+        smart: '智能推荐'
+    };
+    const handleHospitalSubmit = (mealPlanForm) => {
+        hospitalIndexRef.value ?.submitForm(mealPlanForm);
+    };
+    const handleDailySubmit = (mealPlanForm) => {
+        console.log(12);
+        
+        dailyIndexRef.value ?.submitForm(mealPlanForm);
+    };
+
+    // 点击标签
+    const handleClick = (tab: TabsPaneContext) => {
+        console.log(tab.paneName);
+    };
+
+    // 页面跳转处理
+    const handlePageChange = (pageType: PageType) => {
+        type.value = pageType;
+    };
+
+    // 各个按钮的点击处理
+    const handleHospital = () => {
+        handlePageChange(PageType.HOSPITAL);
+    };
+    // 平衡膳食
+    const handleBalance = () => {
+        handlePageChange(PageType.BALANCE);
+    };
+
+    // 日常膳食
+    const handleDaily = () => {
+        handlePageChange(PageType.DAILY);
+    };
+
+    // 智能推荐
+    const handleSmartRecommend = () => {
+        handlePageChange(PageType.SMART);
+    };
+
+    // 返回列表页
+    const isType = () => {
+        handlePageChange(PageType.LIST);
+    };
 </script>
 <style scoped>
-.demo-tabs {
-  background: #f7f9fb;
-  border-radius: 12px 12px 0 0;
-  padding: 16px 0 0 16px;
-  min-height: 80px;
-}
-
-.btn_content {
-  margin-top: 20px;
-}
-:deep(.el-tabs__header) {
-  border-bottom: none !important;
-  margin-bottom: 0 !important;
-  background: transparent;
-}
-:deep(.el-tabs__nav) {
-  background: transparent;
-  border: none;
-  align-items: center;
-  height: 72px;
-  display: flex;
-}
-:deep(.el-tabs__content) {
-  background: #fff !important;
-  height: 600px;
-}
-:deep(.el-tabs__item) {
-  background: transparent !important;
-  position: relative;
-  z-index: 1;
-  font-size: 16px;
-  color: #333;
-  padding: 0 48px !important;
-  height: 64px;
-  line-height: 64px;
-  display: flex;
-  align-items: center;
-  transition: color 0.2s;
-  border: none !important;
-}
-:deep(.el-tabs__item.is-active) {
-  color: #409eff !important;
-  z-index: 2;
-}
-:deep(.el-tabs__item.is-active)::before {
-  content: '';
-  position: absolute;
-  left: 0;
-  right: 0;
-  top: 0;
-  bottom: 0;
-  background: #fff;
-  border-radius: 12px 12px 0 0;
-  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.08);
-  z-index: -1;
-}
-
-.diet-content-area {
-  background: #fff;
-  border-radius: 0 0 16px 16px;
-  min-height: 300px;
-  /* 让内容区和Tabs无缝衔接 */
-  margin-top: -24px;
-  padding: 32px 40px;
-  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.04);
-  position: relative;
-  z-index: 1;
-}
-</style>
+    .demo-tabs {
+        width: 90vw;
+        background: #f7f9fb;
+        border-radius: 12px 12px 0 0;
+        padding: 16px 0 0 16px;
+        min-height: 800px;
+    }
+
+    .btn_content {
+        margin-top: 20px;
+    }
+
+    :deep(.el-tabs__header) {
+        border-bottom: none !important;
+        margin-bottom: 0 !important;
+        background: transparent;
+    }
+
+    :deep(.el-tabs__nav) {
+        background: transparent;
+        border: none;
+        align-items: center;
+        height: 72px;
+        display: flex;
+    }
+
+    :deep(.el-tabs__content) {
+        background: #fff !important;
+        height: 600px;
+    }
+
+    :deep(.el-tabs__item) {
+        background: transparent !important;
+        position: relative;
+        z-index: 1;
+        font-size: 16px;
+        color: #333;
+        padding: 0 48px !important;
+        height: 64px;
+        line-height: 64px;
+        display: flex;
+        align-items: center;
+        transition: color 0.2s;
+        border: none !important;
+    }
+
+    :deep(.el-tabs__item.is-active) {
+        color: #409eff !important;
+        z-index: 2;
+    }
+
+    :deep(.el-tabs__item.is-active)::before {
+        content: '';
+        position: absolute;
+        left: 0;
+        right: 0;
+        top: 0;
+        bottom: 0;
+        background: #fff;
+        border-radius: 12px 12px 0 0;
+        box-shadow: 0 2px 8px rgba(64, 158, 255, 0.08);
+        z-index: -1;
+    }
+
+    .diet-content-area {
+        background: #fff;
+        border-radius: 0 0 16px 16px;
+        min-height: 300px;
+        /* 让内容区和Tabs无缝衔接 */
+        margin-top: -24px;
+        padding: 32px 40px;
+        box-shadow: 0 2px 8px rgba(64, 158, 255, 0.04);
+        position: relative;
+        z-index: 1;
+    }
+</style>

+ 1 - 12
src/views/patients/medicalRecord/index.vue

@@ -509,21 +509,10 @@ onMounted(() => {
   padding: 20px;
   border-radius: 4px;
   margin-bottom: 20px;
-  width: 86%;
+  width: 87%;
   margin-left: 30px;
 }
 
-    .patient-right-info {
-        background-color: #fff;
-        height: 100px;
-        padding: 20px;
-        border-radius: 4px;
-        margin-bottom: 20px;
-        width: 86%;
-        margin-left: 30px;
-
-    }
-
     .info-row {
         display: flex;
         align-items: center;

+ 10 - 7
src/views/patients/medicalRecord/levelMenu.vue

@@ -10,7 +10,7 @@
             <el-menu-item index="visitRecord">
                 <span>检查指标</span>
             </el-menu-item>
-            <el-menu-item index="checkLabel">
+            <el-menu-item index="nutritionScreening">
                 <span>营养筛查</span>
             </el-menu-item>
             <el-menu-item index="nutritionEvaluation">
@@ -22,10 +22,10 @@
             <el-menu-item index="nutritionPlan">
                 <span>营养干预</span>
             </el-menu-item>
-            <el-menu-item index="nutritionMonitor"> 
+            <el-menu-item index="nutritionMonitor">
                 <span>营养膳食</span>
             </el-menu-item>
-            <el-menu-item index="checkRoom" v-show="props.patientInfo.type=='1'"> 
+            <el-menu-item index="checkRoom" v-show="props.patientInfo.type=='1'">
                 <span>查房记录</span>
             </el-menu-item>
         </el-menu>
@@ -34,7 +34,7 @@
     <div>
         <!-- <keep-alive>
         </keep-alive> -->
-                    <component :is="currentComponent" v-if="currentComponent" :patient-info="props.patientInfo"/>
+        <component :is="currentComponent" v-if="currentComponent" :patient-info="props.patientInfo" />
 
     </div>
 
@@ -48,19 +48,22 @@
     const VisitRecord = defineAsyncComponent(() => import('@/views/patients/medicalRecord/levelMenu/visitRecord/index.vue'));
 
     const NutriDiagnosis = defineAsyncComponent(() => import('@/views/patients/medicalRecord/levelMenu/nutriDiagnosis/index.vue'));
-
+    const NutritionScreening = defineAsyncComponent(() => import('@/views/patients/medicalRecord/levelMenu/nutritionScreening/index.vue'));
+    const NutritionEvaluation = defineAsyncComponent(() => import('@/views/patients/medicalRecord/levelMenu/nutritionEvaluation/index.vue'));
     const activeMenu = ref('medicalRecord');
 
     const componentMap = {
         medicalRecord: MedicalRecord,
         visitRecord: VisitRecord,
         nutritionDiagnosis: NutriDiagnosis,
+        nutritionScreening: NutritionScreening,
+        nutritionEvaluation: NutritionEvaluation,
         // ... 其它映射
     };
 
     const currentComponent = ref(componentMap['medicalRecord']); // 默认显示
 
-   // 声明接收的 props
+    // 声明接收的 props
     const props = defineProps({
         patientInfo: {
             type: Object,
@@ -116,7 +119,7 @@
     .medical-menu {
         display: inline-flex;
         min-width: max-content;
-        width: 90% ;
+        width: 90%;
     }
 
     .patient-info {

+ 197 - 0
src/views/patients/medicalRecord/levelMenu/nutritionScreening/index.vue

@@ -0,0 +1,197 @@
+<template>
+    <div class="p-2">
+        <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+            <div v-show="showSearch" class="mb-[10px]">
+                <el-card shadow="hover">
+                    <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+                        <el-form-item label="筛查类型" prop="configId">
+                            <div>
+                                <el-select v-model="queryParams.configId" placeholder="请选择" clearable>
+                                    <el-option v-for="item in screeningAssessmentConfigList" :key="item.configId" :label="item.name" :value="item.configId" />
+                                </el-select>
+                            </div>
+                        </el-form-item>
+                        <el-form-item>
+                            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+                        </el-form-item>
+                    </el-form>
+                </el-card>
+            </div>
+        </transition>
+
+        <el-card shadow="never">
+            <template #header>
+                <el-row :gutter="10" class="mb8">
+                    <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+                </el-row>
+            </template>
+            <el-table v-loading="loading" border :data="screeningList" @selection-change="handleSelectionChange">
+                <el-table-column type="selection" width="55" align="center" />
+                <el-table-column label="营养筛查时间" align="center" prop="screeningTime" />
+                <el-table-column label="营养筛查类型" align="center" prop="configName" />
+                <el-table-column label="营养筛查分数" align="center" prop="screeningScore" />
+                <el-table-column label="营养筛查结论" align="center" prop="screeningConclusion" />
+                <el-table-column label="筛查医生/护士" align="center" prop="createByName" />
+                <!-- <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+                    <template #default="scope">
+                        <el-tooltip content="详情" placement="top">
+                            <el-button link type="primary" icon="View" @click="handleView(scope.row)">详情</el-button>
+                        </el-tooltip>
+                    </template>
+                </el-table-column> -->
+            </el-table>
+            <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+        </el-card>
+        <!-- 添加或修改营养筛查对话框 -->
+
+    </div>
+</template>
+
+<script setup name="NutritionScreening" lang="ts">
+    import { listScreening, } from '@/api/patients/screening';
+    import { ScreeningVO, ScreeningQuery, ScreeningForm } from '@/api/patients/screening/types';
+    import { listScreeningAssessmentConfig } from '@/api/system/screeningAssessmentConfig';
+    import { ScreeningAssessmentConfigQuery, ScreeningAssessmentConfigVO } from '@/api/system/screeningAssessmentConfig/types';
+
+    const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+    const { treatment_user_type } = toRefs < any > (proxy ?.useDict('treatment_user_type'));
+
+    const screeningTime = ref < [DateModelType, DateModelType] > (['', '']);
+    const screeningList = ref < ScreeningVO[] > ([]);
+    const buttonLoading = ref(false);
+    const loading = ref(true);
+    const showSearch = ref(true);
+    const ids = ref < Array < string | number >> ([]);
+    const single = ref(true);
+    const multiple = ref(true);
+    const total = ref(0);
+    const screeningAssessmentConfigList = ref < ScreeningAssessmentConfigVO[] > ([]);
+    const router = useRouter();
+    const queryFormRef = ref < ElFormInstance > ();
+    const screeningFormRef = ref < ElFormInstance > ();
+    const { patientInfo } = defineProps < { patientInfo: any } > ()
+    const emit = defineEmits(['change'])
+
+    const dialog = reactive < DialogOption > ({
+        visible: false,
+        title: ''
+    });
+
+    const initFormData: ScreeningForm = {
+        id: undefined,
+        patientId: undefined,
+        configId: undefined,
+        screeningTime: undefined,
+        visitType: undefined,
+        patientNo: undefined,
+        screeningScore: undefined,
+        screeningConclusion: undefined,
+        paymentStatus: undefined,
+        status: undefined,
+    }
+    const data = reactive < PageData < ScreeningForm,
+        ScreeningQuery >> ({
+            form: { ...initFormData },
+            queryParams: {
+                pageNum: 1,
+                pageSize: 10,
+                patientId: undefined,
+                configId: undefined,
+                screeningTime: undefined,
+                visitType: undefined,
+                patientNo: undefined,
+                screeningScore: undefined,
+                screeningConclusion: undefined,
+                paymentStatus: undefined,
+                status: undefined,
+                params: {}
+            },
+            rules: {
+               
+            }
+        });
+
+    const { queryParams, form, rules } = toRefs(data);
+
+    /** 查询筛查/评估配置列表 */
+    const getScreeningConfigList = async () => {
+        let param = ref < ScreeningAssessmentConfigQuery > ({
+            pageNum: 1,
+            type: '1',
+            pageSize: -1,
+        });
+        const res = await listScreeningAssessmentConfig(param.value);
+        screeningAssessmentConfigList.value = res.rows;
+    }
+
+    /** 查询营养筛查列表 */
+    const getList = async () => {
+        loading.value = true;
+        queryParams.value.patientId = patientInfo.id;
+        const res = await listScreening(proxy ?.addDateRange(queryParams.value, screeningTime.value));
+        screeningList.value = res.rows;
+        total.value = res.total;
+        loading.value = false;
+    }
+    const handleView = async (row ? : ScreeningVO) => {
+        emit('change', 'nutritionScreeningAdd', window.btoa(row.id.toString()))
+    }
+    /** 取消按钮 */
+    const cancel = () => {
+        reset();
+        dialog.visible = false;
+    }
+
+    /** 表单重置 */
+    const reset = () => {
+        form.value = { ...initFormData };
+        screeningFormRef.value ?.resetFields();
+    }
+
+    /** 搜索按钮操作 */
+    const handleQuery = () => {
+        queryParams.value.pageNum = 1;
+        getList();
+    }
+
+
+    /** 多选框选中数据 */
+    const handleSelectionChange = (selection: ScreeningVO[]) => {
+        ids.value = selection.map(item => item.id);
+        single.value = selection.length != 1;
+        multiple.value = !selection.length;
+    }
+
+
+    function formatDate(date) {
+        const pad = n => n < 10 ? '0' + n : n;
+        return date.getFullYear() + '-' +
+            pad(date.getMonth() + 1) + '-' +
+            pad(date.getDate());
+    }
+
+    onMounted(() => {
+        getList();
+        getScreeningConfigList();
+
+        let now = new Date();
+        let start = formatDate(now);
+        let end = formatDate(new Date(now.getTime() - 24 * 60 * 60 * 1000 * 30));
+        screeningTime.value = [end + ' 00:00:00', start + ' 23:59:59'];
+    });
+</script>
+
+<style scoped>
+    .screening-type-grid {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 8px;
+    }
+
+    .screening-type-btn {
+        font-weight: normal;
+        font-size: 16px;
+        height: 48px;
+        border-radius: 8px;
+    }
+</style>

+ 0 - 1
src/views/patients/nutriDiagnosis/index.vue

@@ -81,7 +81,6 @@
                 <el-form-item label="营养诊断依据:" prop="diagnosisBasisId">
                     <el-input v-model="form.diagnosisBasisId" placeholder="请输入营养筛查/评估结果" clearable />
                 </el-form-item>
-
                 <el-form-item label="营养会诊结论:">
                     <div class="consultation-toolbar">
                         <el-button type="danger" plain @click="clearContent">清空记录</el-button>

+ 1 - 1
src/views/patients/nutritionSetting/index.vue

@@ -207,7 +207,7 @@
     const form = ref({ ...initFormData });
     const queryParams = ref({
         patientId: props.patientInfo ?.id,
-        dateRange: [],
+        dateRange: [new Date(), new Date()],
         type: '',
         settingType: '',
         searchValue: '',