Browse Source

Merge branch 'main' of 192.168.1.121:yp_web/wkx-his-web

HuRongxin 1 month ago
parent
commit
220cdfa05b

+ 984 - 0
.specstory/history/2025-06-24_02-08-调整表单label宽度以完全显示.md

@@ -1427,6 +1427,990 @@ _**User**_
 
 _**Assistant**_
 
+下面是一个通用的“食材分类”建表 SQL 示例,包含常见的分类字段(分类ID、分类名称、上级分类、排序、状态、创建/更新时间等)。你可以根据实际业务需求进行调整。
+
+```sql
+CREATE TABLE ingredient_category (
+    id            BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
+    name          VARCHAR(100) NOT NULL COMMENT '分类名称',
+    parent_id     BIGINT DEFAULT NULL COMMENT '上级分类ID',
+    sort          INT DEFAULT 0 COMMENT '排序',
+    status        TINYINT DEFAULT 1 COMMENT '状态(1正常 0停用)',
+    create_time   DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    update_time   DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    remark        VARCHAR(255) DEFAULT NULL COMMENT '备注'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食材分类表';
+```
+
+**字段说明:**
+- `id`:主键,自增
+- `name`:分类名称
+- `parent_id`:上级分类ID(顶级分类为NULL或0)
+- `sort`:排序字段
+- `status`:状态(1正常,0停用)
+- `create_time`/`update_time`:时间戳
+- `remark`:备注
+
+如需其他字段或索引,请告知!
+
+---
+
+_**User**_
+
+请告诉我 如何打包
+
+---
+
+_**Assistant**_
+
+好的!你想要“打包”前端项目(如 Vue 项目),一般指的是**构建生产环境代码**,生成可部署的静态文件。下面是标准的打包流程说明:
+
+---
+
+## 1. 安装依赖
+
+如果你还没有安装依赖,请先运行:
+
+```bash
+npm install
+```
+或
+```bash
+yarn install
+```
+
+---
+
+## 2. 打包命令
+
+大多数 Vue 项目(包括 Vite、Vue CLI、ruoyi-vue 等)都可以用如下命令打包:
+
+```bash
+npm run build
+```
+或
+```bash
+yarn build
+```
+
+---
+
+## 3. 打包结果
+
+打包完成后,会在项目根目录下生成一个 `dist` 文件夹(有些项目可能叫 `build`),里面就是**可部署的静态资源**(HTML、JS、CSS、图片等)。
+
+---
+
+## 4. 部署
+
+将 `dist` 文件夹里的内容上传到你的服务器(如 Nginx、Apache、Tomcat、云服务等),即可对外访问。
+
+---
+
+### 常见问题
+
+- **打包配置**:如需自定义打包路径、环境变量等,可修改 `vite.config.js` 或 `vue.config.js`。
+- **后端接口地址**:生产环境下接口地址通常通过 `.env.production` 文件配置。
+
+---
+
+如果你需要**具体到某个脚手架**(如 Vite、Vue CLI、ruoyi-vue、React 等)的详细打包说明,请告诉我你的项目类型或框架!
+
+---
+
+_**User**_
+
+请告诉我 如何打包
+
+---
+
+_**Assistant**_
+
+你遇到的错误提示是:  
+```
+npm error Missing script: "build"
+```
+这说明你的 `package.json` 文件里**没有配置 `build` 脚本**,所以无法直接用 `npm run build` 打包。
+
+---
+
+## 解决方法
+
+### 1. 查看可用脚本
+
+先运行:
+```bash
+npm run
+```
+看看有哪些可用的脚本(如 `dev`、`start`、`prod` 等)。
+
+---
+
+### 2. 检查 `package.json` 文件
+
+打开项目根目录下的 `package.json` 文件,找到 `"scripts"` 字段。例如:
+
+```json
+"scripts": {
+  "dev": "vite",
+  "serve": "vue-cli-service serve",
+  "start": "node server.js"
+}
+```
+如果没有 `"build"`,你需要**添加或确认打包命令**。
+
+---
+
+### 3. 添加或修正 `build` 脚本
+
+- **Vite 项目**:
+  ```json
+  "build": "vite build"
+  ```
+- **Vue CLI 项目**:
+  ```json
+  "build": "vue-cli-service build"
+  ```
+- **React (Create React App)**:
+  ```json
+  "build": "react-scripts build"
+  ```
+
+将对应的 `"build"` 脚本添加到 `scripts` 里。例如:
+
+```json
+"scripts": {
+  "dev": "vite",
+  "build": "vite build"
+}
+```
+
+---
+
+### 4. 再次打包
+
+保存 `package.json` 后,重新运行:
+```bash
+npm run build
+```
+
+---
+
+## 如果你不确定用什么命令
+
+请把你的 `package.json` 文件里的 `"scripts"` 部分内容发给我,我可以帮你判断应该怎么打包!
+
+---
+
+**总结**:  
+你需要在 `package.json` 的 `"scripts"` 里有 `"build"` 脚本,才能用 `npm run build` 打包。如果没有,就需要手动添加,具体内容取决于你的项目类型。
+
+---
+
+_**User**_
+
+帮我修改来处理这个问题
+
+---
+
+_**Assistant**_
+
+你的项目缺少 `build` 脚本,所以 `npm run build` 会报错。  
+**解决方法**:只需在 `package.json` 的 `"scripts"` 部分添加一个 `build` 脚本即可。
+
+---
+
+### 步骤如下:
+
+1. **打开项目根目录下的 `package.json` 文件**。
+2. **找到 `"scripts"` 字段**,比如:
+
+   ```json
+   "scripts": {
+     "dev": "vite"
+   }
+   ```
+
+3. **添加 `build` 脚本**。  
+   - 如果你的项目是 Vite(大概率是),添加:
+     ```json
+     "build": "vite build"
+     ```
+   - 如果是 Vue CLI,添加:
+     ```json
+     "build": "vue-cli-service build"
+     ```
+   - 如果不确定,先试试 Vite 的。
+
+   最终效果类似这样:
+
+   ```json
+   "scripts": {
+     "dev": "vite",
+     "build": "vite build"
+   }
+   ```
+
+4. **保存文件**,然后重新运行:
+   ```bash
+   npm run build
+   ```
+
+---
+
+如果你不确定用哪个命令,  
+请把你的 `package.json` 文件里的 `"scripts"` 部分内容发给我,我可以帮你精确判断!
+
+需要我帮你自动添加 `build` 脚本吗?如果需要,请告诉我你用的是 Vite 还是 Vue CLI,或者直接把 `package.json` 里的 `"scripts"` 部分发给我。
+
+---
+
+_**User**_
+
+请帮我改正确
+
+---
+
+_**Assistant**_
+
+请将你的 `package.json` 文件中的 `"scripts"` 部分内容粘贴给我,这样我可以帮你**精确地**添加或修改 `build` 脚本,确保打包命令正确无误。
+
+如果你不方便粘贴,也可以告诉我你用的是哪种前端脚手架(如 Vite、Vue CLI、React、Angular 等),我会直接给出适合你的 `build` 脚本内容。
+
+**举例:**  
+如果你的 `scripts` 目前是这样:
+```json
+"scripts": {
+  "dev": "vite"
+}
+```
+你只需要改成:
+```json
+"scripts": {
+  "dev": "vite",
+  "build": "vite build"
+}
+```
+
+**请把你的 `scripts` 部分发给我,我帮你改成正确的!**
+
+---
+
+_**User**_
+
+请帮我修改文件解决这个错误
+
+---
+
+_**Assistant**_
+
+<details>
+            <summary>Searched codebase "package.json" • **1** results</summary>
+        
+| File |
+|------|
+| `undefined` |
+
+</details>
+
+---
+
+Read file: package.json
+
+---
+
+```diff
+    "scripts": {
+      "dev": "vite serve --mode development",
++     "build": "vite build --mode production",
+      "build:prod": "vite build --mode production",
+      "build:dev": "vite build --mode development",
+```
+
+---
+
+已为你在 `package.json` 中添加了标准的 `build` 脚本:
+
+```json
+"build": "vite build --mode production"
+```
+
+现在你可以直接运行:
+```bash
+npm run build
+```
+即可正常打包项目,解决了“Missing script: 'build'”的报错。
+
+如有其他打包或部署问题,欢迎随时提问!
+
+---
+
+_**User**_
+
+请帮我修改文件解决这个错误
+
+---
+
+_**Assistant**_
+
+你的报错信息如下:
+```
+Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'unplugin-element-plus' imported from D:\project\wkx-his-web\node_modules\.vite-temp\vite.config.ts...
+```
+这说明你的项目依赖中**缺少 `unplugin-element-plus`**,而 `vite.config.ts` 里有用到它。
+
+---
+
+## 解决方法
+
+你只需要安装这个依赖即可。  
+在项目根目录下运行:
+
+```bash
+npm install unplugin-element-plus --save-dev
+```
+或者用 yarn:
+```bash
+yarn add unplugin-element-plus --dev
+```
+或者用 pnpm(你的依赖里有 pnpm):
+```bash
+pnpm add unplugin-element-plus -D
+```
+
+---
+
+## 步骤总结
+
+1. 打开终端,进入你的项目目录(`D:/project/wkx-his-web`)。
+2. 运行上面的安装命令。
+3. 安装完成后,再次执行打包命令:
+   ```bash
+   npm run build
+   ```
+
+---
+
+**这样就能解决缺少 `unplugin-element-plus` 的报错!**  
+如果还有其他依赖缺失的报错,按照同样方法安装即可。需要进一步帮助请随时告知!
+
+---
+
+_**User**_
+
+帮我生成食材管理建表sql
+
+---
+
+_**Assistant**_
+
+根据你提供的食材管理字段信息,我为你生成完整的建表SQL:
+
+```sql
+-- 食材分类表
+CREATE TABLE ingredient_category (
+    id            BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
+    name          VARCHAR(100) NOT NULL COMMENT '分类名称',
+    parent_id     BIGINT DEFAULT NULL COMMENT '上级分类ID',
+    sort          INT DEFAULT 0 COMMENT '排序',
+    status        TINYINT DEFAULT 1 COMMENT '状态(1正常 0停用)',
+    create_time   DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    update_time   DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    remark        VARCHAR(255) DEFAULT NULL COMMENT '备注'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食材分类表';
+
+-- 食材管理表
+CREATE TABLE ingredient (
+    id                    BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
+    category_id           BIGINT NOT NULL COMMENT '食材分类ID',
+    name                  VARCHAR(100) NOT NULL COMMENT '食材名称',
+    code                  VARCHAR(50) NOT NULL COMMENT '食材编码',
+    unit                  VARCHAR(20) NOT NULL COMMENT '单位',
+    purchase_price        DECIMAL(10,2) DEFAULT 0.00 COMMENT '入货价格(元)',
+    edible_ratio          DECIMAL(5,2) DEFAULT 100.00 COMMENT '食材可食比例构成(%)',
+    shelf_life            INT DEFAULT 0 COMMENT '保质期(天)',
+    stock_warning         DECIMAL(10,2) DEFAULT 0.00 COMMENT '库存预警值(g)',
+    expiry_warning        INT DEFAULT 0 COMMENT '保质期预警(天)',
+    description           TEXT COMMENT '食材描述',
+    
+    -- 营养成分
+    calories              DECIMAL(8,2) DEFAULT 0.00 COMMENT '热量(kcal)',
+    protein               DECIMAL(8,2) DEFAULT 0.00 COMMENT '蛋白质(g)',
+    fat                   DECIMAL(8,2) DEFAULT 0.00 COMMENT '脂肪(g)',
+    carbohydrate          DECIMAL(8,2) DEFAULT 0.00 COMMENT '碳水化合物(g)',
+    water                 DECIMAL(8,2) DEFAULT 0.00 COMMENT '水分(ml)',
+    vitamin_a             DECIMAL(8,2) DEFAULT 0.00 COMMENT '维生素A(μg)',
+    vitamin_b2            DECIMAL(8,2) DEFAULT 0.00 COMMENT '维生素B2(mg)',
+    vitamin_c             DECIMAL(8,2) DEFAULT 0.00 COMMENT '维生素C(mg)',
+    sodium                DECIMAL(8,2) DEFAULT 0.00 COMMENT '钠(mg)',
+    iron                  DECIMAL(8,2) DEFAULT 0.00 COMMENT '铁(mg)',
+    phosphorus            DECIMAL(8,2) DEFAULT 0.00 COMMENT '磷(mg)',
+    dietary_fiber         DECIMAL(8,2) DEFAULT 0.00 COMMENT '膳食纤维(g)',
+    vitamin_b1            DECIMAL(8,2) DEFAULT 0.00 COMMENT '维生素B1(mg)',
+    niacin                DECIMAL(8,2) DEFAULT 0.00 COMMENT '烟酸(mg)',
+    vitamin_e             DECIMAL(8,2) DEFAULT 0.00 COMMENT '维生素E(mg)',
+    calcium               DECIMAL(8,2) DEFAULT 0.00 COMMENT '钙(mg)',
+    potassium             DECIMAL(8,2) DEFAULT 0.00 COMMENT '钾(mg)',
+    cholesterol           DECIMAL(8,2) DEFAULT 0.00 COMMENT '胆固醇(g)',
+    
+    -- 系统字段
+    status                TINYINT DEFAULT 1 COMMENT '状态(1正常 0停用)',
+    create_time           DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    update_time           DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    create_by             VARCHAR(64) DEFAULT NULL COMMENT '创建者',
+    update_by             VARCHAR(64) DEFAULT NULL COMMENT '更新者',
+    remark                VARCHAR(255) DEFAULT NULL COMMENT '备注',
+    
+    -- 索引
+    INDEX idx_category_id (category_id),
+    INDEX idx_code (code),
+    INDEX idx_name (name),
+    INDEX idx_status (status),
+    
+    -- 外键约束
+    FOREIGN KEY (category_id) REFERENCES ingredient_category(id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食材管理表';
+```
+
+**表结构说明:**
+
+1. **食材分类表 (ingredient_category)**:
+   - 支持多级分类(通过parent_id)
+   - 包含排序和状态管理
+
+2. **食材管理表 (ingredient)**:
+   - 基本信息:名称、编码、单位、价格等
+   - 营养成分:包含所有你列出的营养成分字段
+   - 系统字段:创建时间、更新时间、状态等
+   - 索引优化:提高查询性能
+   - 外键约束:确保数据完整性
+
+**字段类型选择:**
+- 使用 `DECIMAL` 类型存储价格和营养成分,保证精度
+- 使用 `INT` 类型存储天数和预警值
+- 使用 `TEXT` 类型存储描述信息
+
+如果你需要调整字段类型、添加其他字段或修改约束,请告诉我!
+
+---
+
+_**User**_
+
+帮我设置表单的样式,请参考图帮我修改
+
+---
+
+_**Assistant**_
+
+你上传的图片显示了两个表单项(“食材分类”、“食材名称”),它们分别是下拉选择和输入框,且**标签右侧对齐,输入框宽度较大,且有必填红色星号**。  
+你当前的代码是用 `<el-input>` 直接输入“食材分类ID”,而不是下拉选择。
+
+### 参考图片,建议如下:
+
+1. **食材分类**用 `<el-select>`,并设置 `required` 星号。
+2. **表单项标签右对齐**,输入框宽度适中。
+3. **两列布局**,间距适当。
+
+---
+
+### 修改建议代码如下:
+
+```vue
+<el-row :gutter="32">
+  <el-col :span="12">
+    <el-form-item
+      label="食材分类:"
+      prop="foodCategoryId"
+      :rules="[{ required: true, message: '请选择食材分类', trigger: 'change' }]"
+      label-width="100px"
+      required
+    >
+      <el-select v-model="form.foodCategoryId" placeholder="请选择" style="width: 100%;">
+        <el-option
+          v-for="item in categoryOptions"
+          :key="item.id"
+          :label="item.name"
+          :value="item.id"
+        />
+      </el-select>
+    </el-form-item>
+  </el-col>
+  <el-col :span="12">
+    <el-form-item
+      label="食材名称:"
+      prop="name"
+      :rules="[{ required: true, message: '请输入食材名称', trigger: 'blur' }]"
+      label-width="100px"
+      required
+    >
+      <el-input v-model="form.name" placeholder="请输入" style="width: 100%;" />
+    </el-form-item>
+  </el-col>
+</el-row>
+```
+
+#### 说明
+- `label-width="100px"` 保证标签右对齐且宽度一致。
+- `required` 属性和 `rules` 都加上,确保有红色星号和校验。
+- `el-select` 用于分类选择,`categoryOptions` 需在 data 里准备好。
+- `:gutter="32"` 让两列有间距。
+- `style="width: 100%;"` 让输入框和下拉框宽度适中。
+
+---
+
+如需完整表单样式或有其他字段,请告知!
+
+---
+
+_**User**_
+
+帮我改为使用select,可以选择子级
+
+---
+
+_**Assistant**_
+
+你希望将“食材分类”输入框改为可选择子级的下拉选择(通常用 `<el-cascader>` 或 `<el-tree-select>` 实现),这样可以选择多级分类。
+
+这里推荐用 Element Plus 的 `<el-cascader>`,它支持多级联动选择,非常适合你的场景。
+
+---
+
+### 修改建议
+
+#### 1. 代码替换
+
+将你的代码:
+```vue
+<el-col :span="6">
+  <el-form-item label="食材分类" prop="foodCategoryId">
+    <el-input v-model="form.foodCategoryId" placeholder="请输入" />
+  </el-form-item>
+</el-col>
+```
+
+替换为:
+```vue
+<el-col :span="6">
+  <el-form-item
+    label="食材分类"
+    prop="foodCategoryId"
+    :rules="[{ required: true, message: '请选择食材分类', trigger: 'change' }]"
+    required
+  >
+    <el-cascader
+      v-model="form.foodCategoryId"
+      :options="categoryOptions"
+      :props="{ checkStrictly: true, emitPath: false, value: 'id', label: 'name', children: 'children' }"
+      placeholder="请选择"
+      clearable
+      style="width: 100%;"
+    />
+  </el-form-item>
+</el-col>
+```
+
+#### 2. 说明
+- `categoryOptions` 需要是树形结构的数据(如:`[{ id, name, children: [...] }]`)。
+- `checkStrictly: true` 允许选择任意层级(包括子级)。
+- `emitPath: false` 只返回选中节点的 id。
+- `value`、`label`、`children` 属性根据你的数据结构调整。
+
+#### 3. 示例 categoryOptions 数据结构
+```js
+categoryOptions: [
+  {
+    id: 1,
+    name: '蔬菜',
+    children: [
+      { id: 11, name: '叶菜类' },
+      { id: 12, name: '根茎类' }
+    ]
+  },
+  {
+    id: 2,
+    name: '肉类',
+    children: [
+      { id: 21, name: '猪肉' },
+      { id: 22, name: '牛肉' }
+    ]
+  }
+]
+```
+
+---
+
+如需后端接口适配、数据获取或有特殊需求,请告知!
+
+---
+
+_**User**_
+
+Handler dispatch failed; nested exception is java.lang.InternalError: java.lang.reflect.InvocationTargetException
+org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.InternalError: java.lang.reflect.InvocationTargetException
+
+---
+
+_**Assistant**_
+
+你遇到的错误信息是:
+
+```
+Handler dispatch failed; nested exception is java.lang.InternalError: java.lang.reflect.InvocationTargetException
+org.springframework.web.util.NestedServletException: Handler dispatch failed; nested exception is java.lang.InternalError: java.lang.reflect.InvocationTargetException
+```
+
+这类错误是**Spring Boot**(或Spring MVC)在处理请求时,Controller方法内部抛出了异常,最终被Spring捕获并包装成了`NestedServletException`。  
+`InvocationTargetException` 通常表示**反射调用的方法内部抛出了异常**,而不是反射本身的问题。
+
+---
+
+## 如何排查和解决
+
+### 1. 查看详细堆栈日志
+- **最重要**:请查看控制台或日志文件中更详细的异常堆栈(stack trace),它会告诉你**具体是哪个类、哪一行代码、什么异常**导致的。
+- 堆栈底部通常会有类似 `Caused by: ...` 的信息,那个才是根本原因。
+
+### 2. 常见原因
+- Controller方法内部有空指针、类型转换、数据库异常等
+- 依赖注入失败
+- 反射调用的目标方法抛出了异常
+- 数据库字段与实体不匹配
+- 参数校验失败
+
+### 3. 解决步骤
+1. **找到详细异常**:在IDEA或日志中搜索`Caused by`,找到最底层的异常信息。
+2. **定位代码**:根据异常信息定位到具体的Java类和行号。
+3. **修复异常**:根据异常类型(如NullPointerException、SQLException等)修复代码逻辑或配置。
+
+---
+
+## 示例
+
+假设详细日志如下:
+```
+Caused by: java.lang.NullPointerException
+    at com.example.demo.controller.FoodController.addFood(FoodController.java:45)
+```
+说明是`FoodController`的`addFood`方法第45行空指针异常。
+
+---
+
+## 建议
+
+- **请粘贴更详细的异常堆栈(包含Caused by部分)**,我可以帮你快速定位问题。
+- 如果你能提供相关Controller或Service的代码片段,排查会更快。
+
+---
+
+**总结**:  
+你现在看到的只是Spring的“包装异常”,需要看详细日志的“根本原因”部分,才能准确修复。如果需要进一步帮助,请把详细异常堆栈发给我!
+
+---
+
+_**User**_
+
+提交成功后,激活菜单 “营养元素”  
+
+---
+
+_**Assistant**_
+
+你希望在表单提交成功后,自动激活左侧菜单栏的“营养元素”菜单。  
+在 Vue + Element Plus + vue-router 项目中,通常的做法是**跳转到“营养元素”页面**,这样菜单会自动高亮。
+
+### 步骤
+
+1. **获取路由实例**(如果你用的是 `<script setup>`,用 `useRouter()`)。
+2. **在提交成功后跳转**到“营养元素”页面的路由地址(假设为 `/system/nutrient`,请根据实际路由调整)。
+3. **菜单高亮**通常由路由自动控制,无需手动设置。
+
+---
+
+### 修改建议
+
+假设你的“营养元素”页面路由为 `/system/nutrient`,你可以这样修改 `submitForm` 方法:
+
+```javascript
+import { useRouter } from 'vue-router';
+
+const router = useRouter();
+
+const submitForm = () => {
+  foodIngredientFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      let params = ref<FoodIngredientForm>({});
+      Object.assign(params.value, form.value);
+      params.value.foodCategoryId = form.value.foodCategoryIds.join(',');
+      if (form.value.foodIngredientId) {
+        await updateFoodIngredient(params.value).finally(() => buttonLoading.value = false);
+      } else {
+        await addFoodIngredient(params.value).finally(() => buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功");
+      // 跳转到“营养元素”页面
+      router.push('/system/nutrient');
+    }
+  });
+}
+```
+
+---
+
+### 说明
+
+- `router.push('/system/nutrient')` 会跳转页面并自动高亮对应菜单。
+- 如果你的“营养元素”菜单路由不是 `/system/nutrient`,请替换为实际路由地址。
+- 如果你用的是 `proxy.$router`,也可以写成:`proxy.$router.push('/system/nutrient')`。
+
+---
+
+如需**只高亮菜单但不跳转页面**,需要手动设置菜单的 `active` 状态,这通常不推荐,建议直接跳转页面。
+
+如需进一步帮助,请提供“营养元素”菜单的实际路由地址或菜单配置!
+
+---
+
+_**User**_
+
+提交成功后,帮我点击菜单 “营养元素”  
+
+---
+
+_**Assistant**_
+
+在前端项目中,“点击菜单”实际上就是**通过代码跳转到对应的路由**,这样菜单会自动高亮并显示对应页面。  
+你无法用 JS 直接“模拟鼠标点击”左侧菜单项,但**只要跳转到“营养元素”页面的路由,效果和点击菜单一样**。
+
+---
+
+## 实现方法
+
+假设“营养元素”菜单的路由是 `/system/nutrient`,你只需要在提交成功后加一句:
+
+```js
+router.push('/system/nutrient');
+```
+或者(如果你用的是 `proxy.$router`):
+```js
+proxy.$router.push('/system/nutrient');
+```
+
+---
+
+## 代码示例(`<script setup>`)
+
+```javascript
+import { useRouter } from 'vue-router';
+const router = useRouter();
+
+const submitForm = () => {
+  foodIngredientFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      let params = ref<FoodIngredientForm>({});
+      Object.assign(params.value, form.value);
+      params.value.foodCategoryId = form.value.foodCategoryIds.join(',');
+      if (form.value.foodIngredientId) {
+        await updateFoodIngredient(params.value).finally(() => buttonLoading.value = false);
+      } else {
+        await addFoodIngredient(params.value).finally(() => buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功");
+      // 跳转到“营养元素”菜单页面
+      router.push('/system/nutrient');
+    }
+  });
+}
+```
+
+---
+
+## 说明
+
+- 这样做的效果和“点击菜单”完全一致:页面跳转,菜单高亮。
+- 如果你的“营养元素”菜单路由不是 `/system/nutrient`,请替换为实际路由地址。
+- 如果你用的是 `proxy.$router`,用 `proxy.$router.push('你的路由地址')`。
+
+---
+
+如需进一步帮助,请告诉我“营养元素”菜单的实际路由地址或菜单配置!
+
+---
+
+_**User**_
+
+如何触发菜单的点击事件
+
+---
+
+_**Assistant**_
+
+在 Vue + Element Plus 项目中,**菜单的点击事件**通常是通过路由跳转来实现的。  
+你在菜单里写了 `<el-menu-item index="2">营养元素</el-menu-item>`,如果你想**在代码里“触发点击”这个菜单项**,实际上就是让菜单的 `active` 状态变成 `2`,并且(如果有路由)跳转到对应页面。
+
+---
+
+## 推荐做法:通过路由跳转
+
+如果你的菜单是和路由绑定的(比如 `:index="$route.path"` 或 `:index="menu.route"`),只需要:
+
+```js
+router.push('你的营养元素页面路由');
+```
+这样菜单会自动高亮,页面也会切换。
+
+---
+
+## 如果你想手动设置菜单的 active
+
+假设你的 `<el-menu>` 有 `:default-active="activeMenu"`,你可以这样:
+
+```js
+activeMenu.value = '2';
+```
+这样菜单会高亮“营养元素”项,但**页面内容不会自动切换**,除非你自己处理。
+
+---
+
+## 如果你想“模拟点击”菜单项
+
+**Element Plus 没有直接提供“模拟点击”菜单项的方法**,但你可以通过以下方式实现:
+
+### 1. 通过 ref 调用菜单的事件(不推荐,复杂且不优雅)
+
+### 2. 推荐:直接调用菜单点击的处理函数
+
+如果你有如下菜单点击事件:
+
+```vue
+<el-menu @select="handleMenuSelect" ...>
+  <el-menu-item index="2">营养元素</el-menu-item>
+</el-menu>
+```
+
+你可以在代码里直接调用:
+
+```js
+handleMenuSelect('2');
+```
+
+---
+
+## 总结
+
+- **最佳实践**:直接用 `router.push('路由地址')`,菜单和页面都会切换。
+- 如果你只想高亮菜单项,设置 `activeMenu.value = '2'`。
+- 如果你有自定义的菜单点击处理函数,可以直接调用它。
+
+---
+
+**如果你能提供菜单的完整代码(el-menu部分),我可以给你最适合你项目的写法!**
+
+---
+
+_**User**_
+
+@add.vue 修改这个文件代码,将 form.value.foodCategoryIds数组转化为数值数组
+
+---
+
+_**Assistant**_
+
+根据你的需求,你想将  
+```js
+form.value.foodCategoryIds = res.data.foodCategoryId.toString().split(',')
+```
+转为**数值数组**,即每个元素都为 `Number` 类型。
+
+---
+
+### 修改建议
+
+请将该行代码修改为:
+
+```js
+form.value.foodCategoryIds = res.data.foodCategoryId
+  .toString()
+  .split(',')
+  .map(id => Number(id));
+```
+
+---
+
+### 对比修改
+
+原代码:
+```js
+form.value.foodCategoryIds = res.data.foodCategoryId.toString().split(',')
+```
+
+修改后:
+```js
+form.value.foodCategoryIds = res.data.foodCategoryId
+  .toString()
+  .split(',')
+  .map(id => Number(id));
+```
+
+---
+
+这样,`form.value.foodCategoryIds` 就会变成一个**数值类型的数组**,如 `[1, 2, 3]`。
+
+如需进一步处理空字符串或异常情况,可以用:
+```js
+form.value.foodCategoryIds = res.data.foodCategoryId
+  ? res.data.foodCategoryId.toString().split(',').map(id => Number(id))
+  : [];
+```
+
+如有更多需求请随时告知!
+
+---
+
+_**User**_
+
+转化为大整数
+
+---
+
+_**Assistant**_
+
+你希望将 `form.value.foodCategoryIds` 转化为**大整数数组**,即每个元素为 `BigInt` 类型。
+
+### 推荐写法
+
+将原来的:
+```js
+form.value.foodCategoryIds = res.data.foodCategoryId
+  .toString()
+  .split(',')
+  .map(id => Number(id));
+```
+改为:
+```js
+form.value.foodCategoryIds = res.data.foodCategoryId
+  ? res.data.foodCategoryId.toString().split(',').map(id => BigInt(id))
+  : [];
+```
+
+这样,`form.value.foodCategoryIds` 就是一个 `BigInt` 数组,例如 `[1n, 2n, 3n]`。
+
+---
+
+如需自动修正代码,我可以帮你直接修改对应文件。是否需要我为你自动修改 `add.vue` 文件?
+
+---
+
+_**User**_
+
+后端返回的是long类型,前端数值失真了,如何处理
+
+---
+
+_**Assistant**_
+
 
 
 ---

+ 1 - 0
package.json

@@ -8,6 +8,7 @@
   "type": "module",
   "scripts": {
     "dev": "vite serve --mode development",
+    "build": "vite build --mode production",
     "build:prod": "vite build --mode production",
     "build:dev": "vite build --mode development",
     "preview": "vite preview",

+ 2 - 0
src/api/system/foodCategory/types.ts

@@ -25,6 +25,8 @@ export interface FoodCategoryVO {
   status: string;
 
   isDefault?:number;
+
+  children?: FoodCategoryVO[];
 }
 
 export interface FoodCategoryForm extends BaseEntity {

+ 63 - 0
src/api/system/foodIngredient/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { FoodIngredientVO, FoodIngredientForm, FoodIngredientQuery } from '@/api/system/foodIngredient/types';
+
+/**
+ * 查询食材管理列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listFoodIngredient = (query?: FoodIngredientQuery): AxiosPromise<FoodIngredientVO[]> => {
+  return request({
+    url: '/system/foodIngredient/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询食材管理详细
+ * @param foodIngredientId
+ */
+export const getFoodIngredient = (foodIngredientId: string | number): AxiosPromise<FoodIngredientVO> => {
+  return request({
+    url: '/system/foodIngredient/' + foodIngredientId,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增食材管理
+ * @param data
+ */
+export const addFoodIngredient = (data: FoodIngredientForm) => {
+  return request({
+    url: '/system/foodIngredient',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改食材管理
+ * @param data
+ */
+export const updateFoodIngredient = (data: FoodIngredientForm) => {
+  return request({
+    url: '/system/foodIngredient',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除食材管理
+ * @param foodIngredientId
+ */
+export const delFoodIngredient = (foodIngredientId: string | number | Array<string | number>) => {
+  return request({
+    url: '/system/foodIngredient/' + foodIngredientId,
+    method: 'delete'
+  });
+};

+ 463 - 0
src/api/system/foodIngredient/types.ts

@@ -0,0 +1,463 @@
+export interface FoodIngredientVO {
+  /**
+   * 主键ID
+   */
+  foodIngredientId: string | number;
+
+  /**
+   * 食材分类ID
+   */
+  foodCategoryId: string | number;
+
+  /**
+   * 食材名称
+   */
+  name: string;
+
+  /**
+   * 食材编码
+   */
+  code: string;
+
+  /**
+   * 单位
+   */
+  unit: string;
+
+  /**
+   * 入货价格(元)
+   */
+  purchasePrice: number;
+
+  /**
+   * 食材可食比例构成(%)
+   */
+  edibleRatio: number;
+
+  /**
+   * 保质期(天)
+   */
+  shelfLife: number;
+
+  /**
+   * 库存预警值(g)
+   */
+  stockWarning: number;
+
+  /**
+   * 保质期预警(天)
+   */
+  expiryWarning: number;
+
+  /**
+   * 食材描述
+   */
+  description: string;
+
+  /**
+   * 热量(kcal)
+   */
+  calories: number;
+
+  /**
+   * 蛋白质(g)
+   */
+  protein: number;
+
+  /**
+   * 脂肪(g)
+   */
+  fat: number;
+
+  /**
+   * 碳水化合物(g)
+   */
+  carbohydrate: number;
+
+  /**
+   * 水分(ml)
+   */
+  water: number;
+
+  /**
+   * 维生素A(μg)
+   */
+  vitaminA: number;
+
+  /**
+   * 维生素B2(mg)
+   */
+  vitaminB2: number;
+
+  /**
+   * 维生素C(mg)
+   */
+  vitaminC: number;
+
+  /**
+   * 钠(mg)
+   */
+  sodium: number;
+
+  /**
+   * 铁(mg)
+   */
+  iron: number;
+
+  /**
+   * 磷(mg)
+   */
+  phosphorus: number;
+
+  /**
+   * 膳食纤维(g)
+   */
+  dietaryFiber: number;
+
+  /**
+   * 维生素B1(mg)
+   */
+  vitaminB1: number;
+
+  /**
+   * 烟酸(mg)
+   */
+  niacin: number;
+
+  /**
+   * 维生素E(mg)
+   */
+  vitaminE: number;
+
+  /**
+   * 钙(mg)
+   */
+  calcium: number;
+
+  /**
+   * 钾(mg)
+   */
+  potassium: number;
+
+  /**
+   * 胆固醇(g)
+   */
+  cholesterol: number;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+}
+
+export interface FoodIngredientForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  foodIngredientId?: string | number;
+
+  /**
+   * 食材分类ID
+   */
+  foodCategoryId?: string | number;
+
+  foodCategoryIds?: Array<string> | Array<number>;
+
+  /**
+   * 食材名称
+   */
+  name?: string;
+
+  /**
+   * 食材编码
+   */
+  code?: string;
+
+  /**
+   * 单位
+   */
+  unit?: string;
+
+  /**
+   * 入货价格(元)
+   */
+  purchasePrice?: number;
+
+  /**
+   * 食材可食比例构成(%)
+   */
+  edibleRatio?: number;
+
+  /**
+   * 保质期(天)
+   */
+  shelfLife?: number;
+
+  /**
+   * 库存预警值(g)
+   */
+  stockWarning?: number;
+
+  /**
+   * 保质期预警(天)
+   */
+  expiryWarning?: number;
+
+  /**
+   * 食材描述
+   */
+  description?: string;
+
+  /**
+   * 热量(kcal)
+   */
+  calories?: number;
+
+  /**
+   * 蛋白质(g)
+   */
+  protein?: number;
+
+  /**
+   * 脂肪(g)
+   */
+  fat?: number;
+
+  /**
+   * 碳水化合物(g)
+   */
+  carbohydrate?: number;
+
+  /**
+   * 水分(ml)
+   */
+  water?: number;
+
+  /**
+   * 维生素A(μg)
+   */
+  vitaminA?: number;
+
+  /**
+   * 维生素B2(mg)
+   */
+  vitaminB2?: number;
+
+  /**
+   * 维生素C(mg)
+   */
+  vitaminC?: number;
+
+  /**
+   * 钠(mg)
+   */
+  sodium?: number;
+
+  /**
+   * 铁(mg)
+   */
+  iron?: number;
+
+  /**
+   * 磷(mg)
+   */
+  phosphorus?: number;
+
+  /**
+   * 膳食纤维(g)
+   */
+  dietaryFiber?: number;
+
+  /**
+   * 维生素B1(mg)
+   */
+  vitaminB1?: number;
+
+  /**
+   * 烟酸(mg)
+   */
+  niacin?: number;
+
+  /**
+   * 维生素E(mg)
+   */
+  vitaminE?: number;
+
+  /**
+   * 钙(mg)
+   */
+  calcium?: number;
+
+  /**
+   * 钾(mg)
+   */
+  potassium?: number;
+
+  /**
+   * 胆固醇(g)
+   */
+  cholesterol?: number;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+}
+
+export interface FoodIngredientQuery extends PageQuery {
+
+  /**
+   * 食材分类ID
+   */
+  foodCategoryId?: string | number;
+
+  /**
+   * 食材名称
+   */
+  name?: string;
+
+  /**
+   * 食材编码
+   */
+  code?: string;
+
+  /**
+   * 单位
+   */
+  unit?: string;
+
+  /**
+   * 入货价格(元)
+   */
+  purchasePrice?: number;
+
+  /**
+   * 食材可食比例构成(%)
+   */
+  edibleRatio?: number;
+
+  /**
+   * 保质期(天)
+   */
+  shelfLife?: number;
+
+  /**
+   * 库存预警值(g)
+   */
+  stockWarning?: number;
+
+  /**
+   * 保质期预警(天)
+   */
+  expiryWarning?: number;
+
+  /**
+   * 食材描述
+   */
+  description?: string;
+
+  /**
+   * 热量(kcal)
+   */
+  calories?: number;
+
+  /**
+   * 蛋白质(g)
+   */
+  protein?: number;
+
+  /**
+   * 脂肪(g)
+   */
+  fat?: number;
+
+  /**
+   * 碳水化合物(g)
+   */
+  carbohydrate?: number;
+
+  /**
+   * 水分(ml)
+   */
+  water?: number;
+
+  /**
+   * 维生素A(μg)
+   */
+  vitaminA?: number;
+
+  /**
+   * 维生素B2(mg)
+   */
+  vitaminB2?: number;
+
+  /**
+   * 维生素C(mg)
+   */
+  vitaminC?: number;
+
+  /**
+   * 钠(mg)
+   */
+  sodium?: number;
+
+  /**
+   * 铁(mg)
+   */
+  iron?: number;
+
+  /**
+   * 磷(mg)
+   */
+  phosphorus?: number;
+
+  /**
+   * 膳食纤维(g)
+   */
+  dietaryFiber?: number;
+
+  /**
+   * 维生素B1(mg)
+   */
+  vitaminB1?: number;
+
+  /**
+   * 烟酸(mg)
+   */
+  niacin?: number;
+
+  /**
+   * 维生素E(mg)
+   */
+  vitaminE?: number;
+
+  /**
+   * 钙(mg)
+   */
+  calcium?: number;
+
+  /**
+   * 钾(mg)
+   */
+  potassium?: number;
+
+  /**
+   * 胆固醇(g)
+   */
+  cholesterol?: number;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 2 - 2
src/views/system/dept/index.vue

@@ -28,11 +28,11 @@
       <template #header>
         <el-row :gutter="10">
           <el-col :span="1.5">
-            <el-button v-hasPermi="['system:dept:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增 </el-button>
+            <el-button v-hasPermi="['system:dept:add']" type="primary"   icon="Plus" @click="handleAdd()">新增 </el-button>
           </el-col>
 
           <el-col :span="1.5">
-            <el-button type="primary" plain  @click="handleExpand" v-hasPermi="['system:foodCategory:add']">{{btnText}}</el-button>
+            <el-button @click="handleExpand" v-hasPermi="['system:foodCategory:add']">{{btnText}}</el-button>
           </el-col>
 
           <!-- <el-col :span="1.5">

+ 7 - 6
src/views/system/diseaseLabel/index.vue

@@ -29,7 +29,7 @@
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5">
-            <el-button type="primary" plain icon="Plus" @click="handleAdd"
+            <el-button type="primary"   icon="Plus" @click="handleAdd"
               v-hasPermi="['system:diseaseLabel:add']">新增</el-button>
           </el-col>
           <!-- <el-col :span="1.5">
@@ -39,15 +39,15 @@
             <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:diseaseLabel:export']">导出</el-button>
           </el-col> -->
           <el-col :span="1.5">
-            <el-button type="warning" plain icon="Download" @click="handleImport"
-              v-hasPermi="['system:diseaseLabel:export']">Excel快速导入</el-button>
+            <el-button type="primary" plain icon="Upload" @click="handleImport"
+              v-hasPermi="['system:diseaseLabel:import']">Excel快速导入</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="warning" plain icon="Download" @click="downLoadTemplate"
+            <el-button type="primary" plain icon="Download" @click="downLoadTemplate"
               v-hasPermi="['system:diseaseLabel:export']">Excel模板下载</el-button>
           </el-col>
           <el-col :span="1.5">
-            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()"
+            <el-button type="danger"  icon="Delete" :disabled="multiple" @click="handleDelete()"
               v-hasPermi="['system:diseaseLabel:remove']">批量删除</el-button>
           </el-col>
           <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
@@ -311,6 +311,7 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
     dialogFile.visible = false;
     fileList.value = [];
     proxy?.$modal.closeLoading();
+    getList();
   } else {
     proxy?.$modal.msgError(res.msg);
     proxy?.$modal.closeLoading();
@@ -323,7 +324,7 @@ const handleUploadSuccess = (res: any, file: UploadFile) => {
 const handleBeforeUpload = (file: any) => {
    const isLt = file.size / 1024 / 1024 < 10;
     if (!isLt) {
-      proxy?.$modal.msgError(`上传头像图片大小不能超过10MB!`);
+      proxy?.$modal.msgError(`上传文件大小不能超过10MB!`);
       return false;
     }       
   proxy?.$modal.loading('正在上传文件,请稍候...');

+ 30 - 10
src/views/system/foodCategory/index.vue

@@ -20,11 +20,11 @@
       <template #header>
         <el-row :gutter="10" class="mb8">
           <el-col :span="1.5">
-            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:foodCategory:add']">新增分类</el-button>
+            <el-button type="primary" icon="Plus" @click="handleAdd" v-hasPermi="['system:foodCategory:add']">新增分类</el-button>
           </el-col>
 
           <el-col :span="1.5">
-            <el-button type="primary" plain  @click="handleExpand" v-hasPermi="['system:foodCategory:add']">{{btnText}}</el-button>
+            <el-button  @click="handleExpand" v-hasPermi="['system:foodCategory:add']">{{btnText}}</el-button>
           </el-col>
 
           <!-- <el-col :span="1.5">
@@ -55,11 +55,11 @@
         <el-table-column label="状态" align="center" prop="status" /> -->
         <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" v-if="scope.row.isDefault != 1" @click="handleUpdate(scope.row)" v-hasPermi="['system:foodCategory:edit']"></el-button>
+            <el-tooltip content="编辑" placement="top">
+              <el-button link type="primary" icon="Edit" v-if="scope.row.isDefault != 1" @click="handleUpdate(scope.row)" v-hasPermi="['system:foodCategory:edit']">编辑</el-button>
             </el-tooltip>
             <el-tooltip content="删除" placement="top">
-              <el-button link type="primary" icon="Delete" v-if="scope.row.isDefault != 1" @click="handleDelete(scope.row)" v-hasPermi="['system:foodCategory:remove']"></el-button>
+              <el-button link type="danger" icon="Delete" v-if="scope.row.isDefault != 1" @click="handleDelete(scope.row)" v-hasPermi="['system:foodCategory:remove']">删除</el-button>
             </el-tooltip>
           </template>
         </el-table-column>
@@ -73,7 +73,7 @@
         <el-form-item label="上级分类" prop="parentId">
               <el-tree-select
               v-model="form.parentId"
-              :data="foodCategoryList"
+              :data="foodCategoryList2"
               :render-after-expand="false" 
               :props="{ value: 'foodCategoryId', label: 'name', children: 'children' } as any"
               check-strictly=true              
@@ -101,9 +101,10 @@ import { changeOssConfigStatus } from '@/api/system/ossConfig';
 const foodCategoryTableRef = ref<ElTableInstance>();
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const isExpandAll = ref(true);
-const expandRowKeys = ref([]);
+const expandRowKeys = ref<string>();
 const btnText = ref('关闭所有');
 const foodCategoryList = ref<FoodCategoryVO[]>([]);
+const foodCategoryList2 = ref<FoodCategoryVO[]>([]);
 const buttonLoading = ref(false);
 const loading = ref(true);
 const showSearch = ref(true);
@@ -114,6 +115,8 @@ const total = ref(0);
 
 const queryFormRef = ref<ElFormInstance>();
 const foodCategoryFormRef = ref<ElFormInstance>();
+const foodTableRef = ref<ElTableInstance>();
+
 
 const dialog = reactive<DialogOption>({
   visible: false,
@@ -160,6 +163,13 @@ const getList = async () => {
   loading.value = false;
 }
 
+/** 查询食材分类列表 */
+const getList2 = async () => {   
+  const res = await listTree();  
+  foodCategoryList2.value = res.rows;
+   
+}
+
 /** 取消按钮 */
 const cancel = () => {
   reset();
@@ -194,19 +204,29 @@ const handleSelectionChange = (selection: FoodCategoryVO[]) => {
 /** 新增按钮操作 */
 const handleAdd = () => {
   reset();
+  getList2();
   dialog.visible = true;
   dialog.title = "添加食材分类";
 }
 
 const handleExpand = () => {
   isExpandAll.value=!isExpandAll.value;  
-  expandRowKeys.value=isExpandAll.value?[]:foodCategoryList.value.map(item=>item.foodCategoryId);
-  btnText.value=isExpandAll.value?'关闭所有':'展开所有';   
-}
+  // expandRowKeys.value=isExpandAll.value?[]:foodCategoryList.value.map(item=>item.foodCategoryId.toString());  
+  btnText.value=isExpandAll.value?'关闭所有':'展开所有';
+  toggleExpandAll(foodCategoryList.value, isExpandAll.value);
+};
+/** 展开/折叠所有 */
+const toggleExpandAll = (data: FoodCategoryVO[], status: boolean) => {
+  data.forEach((item) => {
+    foodCategoryTableRef.value?.toggleRowExpansion(item, status);
+    // if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
+  });
+};
 
 /** 修改按钮操作 */
 const handleUpdate = async (row?: FoodCategoryVO) => {
   reset();
+  getList2();
   const _foodCategoryId = row?.foodCategoryId || ids.value[0]
   const res = await getFoodCategory(_foodCategoryId);
   Object.assign(form.value, res.data);

+ 692 - 0
src/views/system/foodIngredient/add.vue

@@ -0,0 +1,692 @@
+<template>
+  <el-card shadow="always">
+    <el-button type="primary" plain  size="large" @click="close()">返回</el-button>
+    <span style="margin-left: 20px;">{{isView?'查看食材':form.foodIngredientId?'编辑食材':'食材管理'}}</span>
+  </el-card>
+  <div class="p-2">
+    <el-menu class="el-menu-demo" mode="horizontal" @select="handleSelect" :default-active="currentIndex">
+      <el-menu-item index="1">基本信息</el-menu-item>
+      <el-menu-item index="2">营养元素</el-menu-item>
+    </el-menu>
+
+    <el-card shadow="always" v-show="baseInfoIndex==currentIndex">
+      <div  width="1024px"  :style=divStyle>
+        <el-form ref="foodIngredientFormRef" :model="form" :rules="rules" append-to-body label-width="130px">
+          <el-row style="margin-top: 20px;">                  
+            <el-col :span="24" style="margin-left: 100px;">
+              <div><span style="color: red;">&nbsp; </span></div>    
+            </el-col>                   
+          </el-row> 
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="食材分类" prop="foodCategoryId">                
+                  <el-cascader :disabled="isView"
+                  v-model="form.foodCategoryIds"
+                  :options="foodCategoryList"
+                  :props="{value:'foodCategoryId',label:'name'}"  style="width: 100%"                   
+                />
+              </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6">
+              <el-form-item label="食材名称" prop="name">
+                <el-input v-model="form.name" placeholder="请输入" :disabled="isView" />
+              </el-form-item>
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row>
+
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="食材编码" prop="code">
+            <el-input v-model="form.code" placeholder="系统生成"  :disabled="true"/>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6">
+              <el-form-item label="单位" prop="unit">             
+            <el-select v-model="form.unit" placeholder="请选择"   clearable :disabled="isView">
+                <el-option v-for="dict in food_unit" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+              </el-select>
+          </el-form-item>
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row>
+
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="入货价格" prop="purchasePrice">
+            <el-input v-model="form.purchasePrice" placeholder="请输入" :disabled="isView">
+              <template #append><span>元</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6">
+              <el-form-item label="食材可食比例构成" prop="edibleRatio">
+            <el-input v-model="form.edibleRatio" placeholder="请输入" :disabled="isView">
+              <template #append><span>%</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row>
+
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="保质期" prop="shelfLife">
+            <el-input v-model="form.shelfLife" placeholder="请输入" :disabled="isView">
+              <template #append><span>天</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6">
+              <el-form-item label="库存预警值" prop="stockWarning">
+            <el-input v-model="form.stockWarning" placeholder="请输入" :disabled="isView">
+              <template #append><span>g</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row>
+
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="保质期预警" prop="expiryWarning">
+            <el-input v-model="form.expiryWarning" placeholder="请输入" :disabled="isView">
+              <template #append><span>天</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6">               
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row>
+
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="15">
+              <el-form-item label="食材描述" prop="description">
+            <el-input v-model="form.description" type="textarea" placeholder="请输入内容" :rows="10" maxlength="200" show-word-limit :disabled="isView"/>
+          </el-form-item>
+            </el-col>            
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row>
+        </el-form>
+      </div>
+    </el-card>
+
+
+    <el-card shadow="always" v-show="nutrientIndex==currentIndex">
+      <div  width="1024px"  :style=divStyle>        
+        <el-form ref="foodIngredientFormRef" :model="form" :rules="rules" append-to-body label-width="130px"> 
+          <el-row style="margin-top: 20px;">                  
+            <el-col :span="24" style="margin-left: 100px;">
+              <div><span style="color: red;">&nbsp; </span></div>    
+            </el-col>                   
+          </el-row>           
+          <el-row style="margin-bottom: 20px;">            
+            <el-col :span="24" style="margin-left: 100px;">  
+              <div><span style="color: red;font-size: 15px;">注:每100(g/ml) 含量</span></div>                         
+            </el-col>                   
+          </el-row>
+          
+          
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="热量" prop="calories">
+            <el-input v-model="form.calories" placeholder="请输入" :disabled="isView">
+              <template #append><span>kcal</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6"> 
+              <el-form-item label="蛋白质" prop="protein">
+            <el-input v-model="form.protein" placeholder="请输入" :disabled="isView">
+              <template #append><span>g</span></template>
+          </el-input>
+          </el-form-item>              
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row> 
+
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="脂肪" prop="fat">
+            <el-input v-model="form.fat" placeholder="请输入" :disabled="isView">
+              <template #append><span>g</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6"> 
+              <el-form-item label="碳水化合物" prop="carbohydrate">
+            <el-input v-model="form.carbohydrate" placeholder="请输入" :disabled="isView">
+              <template #append><span>g</span></template>
+          </el-input>
+          </el-form-item>         
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row>          
+
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="水分" prop="water">
+            <el-input v-model="form.water" placeholder="请输入" :disabled="isView">
+              <template #append><span>ml</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6"> 
+              <el-form-item label="维生素A" prop="vitaminA">
+            <el-input v-model="form.vitaminA" placeholder="请输入" :disabled="isView">
+              <template #append><span>μg</span></template>
+          </el-input>
+          </el-form-item> 
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row> 
+          
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="维生素B2" prop="vitaminB2">
+            <el-input v-model="form.vitaminB2" placeholder="请输入" :disabled="isView">
+              <template #append><span>mg</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6"> 
+              <el-form-item label="维生素C" prop="vitaminC">
+            <el-input v-model="form.vitaminC" placeholder="请输入" :disabled="isView">
+              <template #append><span>mg</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row> 
+
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="钠" prop="sodium">
+            <el-input v-model="form.sodium" placeholder="请输入" :disabled="isView">
+              <template #append><span>mg</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6"> 
+              <el-form-item label="铁" prop="iron">
+            <el-input v-model="form.iron" placeholder="请输入" :disabled="isView">
+              <template #append><span>mg</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row> 
+
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="磷" prop="phosphorus">
+            <el-input v-model="form.phosphorus" placeholder="请输入" :disabled="isView">
+              <template #append><span>mg</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6"> 
+              <el-form-item label="膳食纤维" prop="dietaryFiber">
+            <el-input v-model="form.dietaryFiber" placeholder="请输入" :disabled="isView">
+              <template #append><span>g</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row> 
+
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="维生素B1" prop="vitaminB1">
+            <el-input v-model="form.vitaminB1" placeholder="请输入" :disabled="isView">
+              <template #append><span>mg</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6"> 
+              <el-form-item label="烟酸" prop="niacin">
+            <el-input v-model="form.niacin" placeholder="请输入" :disabled="isView">
+              <template #append><span>mg</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row> 
+
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="维生素E" prop="vitaminE">
+            <el-input v-model="form.vitaminE" placeholder="请输入" :disabled="isView">
+              <template #append><span>mg</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6"> 
+              <el-form-item label="钙" prop="calcium">
+            <el-input v-model="form.calcium" placeholder="请输入" :disabled="isView">
+              <template #append><span>mg</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row> 
+
+          <el-row>            
+            <el-col :span="3" style="margin-left: -100px;">              
+            </el-col>
+            <el-col :span="6">
+              <el-form-item label="钾" prop="potassium">
+            <el-input v-model="form.potassium" placeholder="请输入" :disabled="isView">
+              <template #append><span>mg</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+            <el-col :span="6"> 
+              <el-form-item label="胆固醇" prop="cholesterol">
+            <el-input v-model="form.cholesterol" placeholder="请输入" :disabled="isView">
+              <template #append><span>g</span></template>
+          </el-input>
+          </el-form-item>
+            </el-col>  
+            <el-col :span="3">               
+            </el-col> 
+            <el-col :span="3">               
+            </el-col>  
+          </el-row> 
+        </el-form>
+      </div>
+    </el-card>
+
+    <el-card shadow="always" class="card-footer" v-if="!isView">
+    <div>
+        <el-button type="primary" size="large" plain @click="close" v-hasPermi="['system:hospital:add']">
+          &nbsp;&nbsp;&nbsp;取消&nbsp;&nbsp;&nbsp;
+        </el-button>
+        <el-button type="primary" size="large" @click="submitForm" v-hasPermi="['system:hospital:add']">
+          &nbsp;&nbsp;&nbsp;保存&nbsp;&nbsp;&nbsp;
+        </el-button>
+      </div>
+  </el-card>
+  </div>
+</template>
+
+<script setup name="FoodIngredient" lang="ts">
+import { listFoodIngredient, getFoodIngredient, delFoodIngredient, addFoodIngredient, updateFoodIngredient } from '@/api/system/foodIngredient';
+import { FoodIngredientVO, FoodIngredientQuery, FoodIngredientForm } from '@/api/system/foodIngredient/types';
+import { listTree} from '@/api/system/foodCategory';
+import { FoodCategoryVO} from '@/api/system/foodCategory/types';
+import { object } from 'vue-types';
+import { fa } from 'element-plus/es/locale/index.mjs';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const {  food_unit } = toRefs<any>(proxy?.useDict('food_unit'));
+
+const foodIngredientList = ref<FoodIngredientVO[]>([]);
+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 divStyle = ref({
+  height: '620px'
+});
+
+const queryFormRef = ref<ElFormInstance>();
+const foodIngredientFormRef = ref<ElFormInstance>();
+const baseInfoIndex = ref('1')
+const nutrientIndex  = ref('2')
+const currentIndex  = ref('1')
+const isView  = ref<boolean>(false)
+const foodCategoryList = ref<FoodCategoryVO[]>([]);
+const route = useRoute();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: FoodIngredientForm = {
+  foodIngredientId: undefined,
+  foodCategoryId: undefined,
+  foodCategoryIds: undefined,
+  name: undefined,
+  code: undefined,
+  unit: undefined,
+  purchasePrice: undefined,
+  edibleRatio: undefined,
+  shelfLife: undefined,
+  stockWarning: undefined,
+  expiryWarning: undefined,
+  description: undefined,
+  calories: undefined,
+  protein: undefined,
+  fat: undefined,
+  carbohydrate: undefined,
+  water: undefined,
+  vitaminA: undefined,
+  vitaminB2: undefined,
+  vitaminC: undefined,
+  sodium: undefined,
+  iron: undefined,
+  phosphorus: undefined,
+  dietaryFiber: undefined,
+  vitaminB1: undefined,
+  niacin: undefined,
+  vitaminE: undefined,
+  calcium: undefined,
+  potassium: undefined,
+  cholesterol: undefined,
+  status: undefined,
+}
+const data = reactive<PageData<FoodIngredientForm, FoodIngredientQuery>>({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    foodCategoryId: undefined,
+    name: undefined,
+    code: undefined,
+    unit: undefined,
+    purchasePrice: undefined,
+    edibleRatio: undefined,
+    shelfLife: undefined,
+    stockWarning: undefined,
+    expiryWarning: undefined,
+    description: undefined,
+    calories: undefined,
+    protein: undefined,
+    fat: undefined,
+    carbohydrate: undefined,
+    water: undefined,
+    vitaminA: undefined,
+    vitaminB2: undefined,
+    vitaminC: undefined,
+    sodium: undefined,
+    iron: undefined,
+    phosphorus: undefined,
+    dietaryFiber: undefined,
+    vitaminB1: undefined,
+    niacin: undefined,
+    vitaminE: undefined,
+    calcium: undefined,
+    potassium: undefined,
+    cholesterol: undefined,
+    status: undefined,
+    params: {
+    }
+  },
+  rules: {    
+    foodCategoryId: [
+      { required: true, message: "食材分类ID不能为空", trigger: "blur" }
+    ],
+    name: [
+      { required: true, message: "食材名称不能为空", trigger: "blur" }
+    ],    
+    unit: [
+      { required: true, message: "单位不能为空", trigger: "blur" }
+    ],
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+
+/** 关闭按钮 */
+const close = () => {
+  const obj: RouteLocationNormalized = {
+    fullPath: '',
+    hash: '',
+    matched: [],
+    meta: undefined,
+    name: undefined,
+    params: undefined,
+    query: undefined,
+    redirectedFrom: undefined,
+    path: '/food/foodIngredient'
+  };
+  proxy?.$tab.closeOpenPage(obj);
+};
+
+const getListTree = async () => {   
+  const res = await listTree(queryParams.value);  
+  foodCategoryList.value = res.rows;   
+}
+
+/** 查询食材管理列表 */
+const getList = async () => {
+  const foodIngredientId = route.params && route.params.id;
+  const vfoodIngredientId = route.params && route.params.vid;
+  let id=undefined;
+  if(vfoodIngredientId){
+    isView.value=true;
+    id=vfoodIngredientId;
+  }else{
+    isView.value=false ;
+    id=foodIngredientId;
+  }
+  
+  if (id&&id!='0') {
+    loading.value = true;
+    const res = await getFoodIngredient(id as string);
+    Object.assign(form.value, res.data);
+    form.value.foodCategoryIds=res.data.foodCategoryId.toString().split(',') ;      
+    console.log(form.value.foodCategoryIds) 
+    loading.value = false;
+  }  
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  foodIngredientFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: FoodIngredientVO[]) => {
+  ids.value = selection.map(item => item.foodIngredientId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = "添加食材管理";
+}
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: FoodIngredientVO) => {
+  reset();
+  const _foodIngredientId = row?.foodIngredientId || ids.value[0]
+  const res = await getFoodIngredient(_foodIngredientId);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = "修改食材管理";
+}
+
+/** 提交按钮 */
+const submitForm = () => {
+  foodIngredientFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      let params=ref<FoodIngredientForm>({});
+      Object.assign(params.value, form.value);
+      params.value.foodCategoryId = form.value.foodCategoryIds.join(',')
+      if (form.value.foodIngredientId) {
+        await updateFoodIngredient(params.value).finally(() =>  buttonLoading.value = false);
+      } else {
+        await addFoodIngredient(params.value).then(res=>{
+          form.value.foodIngredientId=res.data;
+        }).finally(() =>  buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功"); 
+      if(currentIndex.value=='2'){
+        close()
+      }else{
+        currentIndex.value='2';   
+      }           
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: FoodIngredientVO) => {
+  const _foodIngredientIds = row?.foodIngredientId || ids.value;
+  await proxy?.$modal.confirm('是否确认删除食材管理编号为"' + _foodIngredientIds + '"的数据项?').finally(() => loading.value = false);
+  await delFoodIngredient(_foodIngredientIds);
+  proxy?.$modal.msgSuccess("删除成功");
+  await getList();
+}
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download('system/foodIngredient/export', {
+    ...queryParams.value
+  }, `foodIngredient_${new Date().getTime()}.xlsx`)
+}
+
+
+const handleSelect = (key: string, keyPath: string[]) => {
+  currentIndex.value=key.toString()   
+  // console.log(key, keyPath,currentIndex)
+}
+
+onMounted(() => {
+  getListTree()
+  getList();
+});
+</script>
+
+<style lang="scss" scoped>
+.card-footer{  
+    z-index: 99;
+    bottom: 0px;
+    left: calc(var(--left-menu-max-width) + var(--app-content-padding));
+    right: 0px;
+    height: 90px;
+    display: flex;
+    justify-content: center;
+    align-items: center;     
+}
+</style>

+ 462 - 0
src/views/system/foodIngredient/index.vue

@@ -0,0 +1,462 @@
+<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="foodCategoryId">                      
+              <el-tree-select clearable
+              v-model="queryParams.foodCategoryId"
+              :data="foodCategoryList"
+              :render-after-expand="false" 
+              :props="{ value: 'foodCategoryId', label: 'name', children: 'children' } as any"
+              check-strictly=true
+            />
+            </el-form-item>
+            <el-form-item label="食材名称" prop="name">
+              <el-input v-model="queryParams.name" placeholder="请输入食材名称" clearable @keyup.enter="handleQuery" />
+            </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"  icon="Plus" @click="handleAdd" v-hasPermi="['system:foodIngredient:add']">新增食材</el-button>
+          </el-col>
+          <!-- <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:foodIngredient:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:foodIngredient:remove']">删除</el-button>
+          </el-col> -->
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Upload" @click="handleImport" v-hasPermi="['system:foodIngredient:export']">导入</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Download" @click="downLoadTemplate" v-hasPermi="['system:foodIngredient:export']">下载导入模板</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="foodIngredientList" @selection-change="handleSelectionChange">
+        <!-- <el-table-column type="selection" width="55" align="center" /> -->
+        <el-table-column label="食材大类" align="center" prop="bigCategory" />
+        <el-table-column label="食材亚类" align="center" prop="subCategory" />
+        <el-table-column label="食材名称" align="center" prop="name" />         
+        <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(false ,scope.row)" v-hasPermi="['system:foodIngredient:edit']">编辑</el-button>
+            </el-tooltip>
+            <el-tooltip content="详情" placement="top">
+              <el-button link type="primary" icon="View" @click="handleUpdate(true,scope.row)" v-hasPermi="['system:foodIngredient:edit']">详情</el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:foodIngredient: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>
+    <!-- 添加或修改食材管理对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="foodIngredientFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="食材分类ID" prop="foodCategoryId">
+          <el-input v-model="form.foodCategoryId" placeholder="请输入食材分类ID" />
+        </el-form-item>
+        <el-form-item label="食材名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入食材名称" />
+        </el-form-item>
+        <el-form-item label="食材编码" prop="code">
+          <el-input v-model="form.code" placeholder="请输入食材编码" />
+        </el-form-item>
+        <el-form-item label="单位" prop="unit">
+          <el-input v-model="form.unit" placeholder="请输入单位" />
+        </el-form-item>
+        <el-form-item label="入货价格" prop="purchasePrice">
+          <el-input v-model="form.purchasePrice" placeholder="请输入入货价格" />
+        </el-form-item>
+        <el-form-item label="食材可食比例构成" prop="edibleRatio">
+          <el-input v-model="form.edibleRatio" placeholder="请输入食材可食比例构成" />
+        </el-form-item>
+        <el-form-item label="保质期" prop="shelfLife">
+          <el-input v-model="form.shelfLife" placeholder="请输入保质期" />
+        </el-form-item>
+        <el-form-item label="库存预警值" prop="stockWarning">
+          <el-input v-model="form.stockWarning" placeholder="请输入库存预警值" />
+        </el-form-item>
+        <el-form-item label="保质期预警" prop="expiryWarning">
+          <el-input v-model="form.expiryWarning" placeholder="请输入保质期预警" />
+        </el-form-item>
+        <el-form-item label="食材描述" prop="description">
+            <el-input v-model="form.description" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+        <el-form-item label="热量" prop="calories">
+          <el-input v-model="form.calories" placeholder="请输入热量" />
+        </el-form-item>
+        <el-form-item label="蛋白质" prop="protein">
+          <el-input v-model="form.protein" placeholder="请输入蛋白质" />
+        </el-form-item>
+        <el-form-item label="脂肪" prop="fat">
+          <el-input v-model="form.fat" placeholder="请输入脂肪" />
+        </el-form-item>
+        <el-form-item label="碳水化合物" prop="carbohydrate">
+          <el-input v-model="form.carbohydrate" placeholder="请输入碳水化合物" />
+        </el-form-item>
+        <el-form-item label="水分" prop="water">
+          <el-input v-model="form.water" placeholder="请输入水分" />
+        </el-form-item>
+        <el-form-item label="维生素A" prop="vitaminA">
+          <el-input v-model="form.vitaminA" placeholder="请输入维生素A" />
+        </el-form-item>
+        <el-form-item label="维生素B2" prop="vitaminB2">
+          <el-input v-model="form.vitaminB2" placeholder="请输入维生素B2" />
+        </el-form-item>
+        <el-form-item label="维生素C" prop="vitaminC">
+          <el-input v-model="form.vitaminC" placeholder="请输入维生素C" />
+        </el-form-item>
+        <el-form-item label="钠" prop="sodium">
+          <el-input v-model="form.sodium" placeholder="请输入钠" />
+        </el-form-item>
+        <el-form-item label="铁" prop="iron">
+          <el-input v-model="form.iron" placeholder="请输入铁" />
+        </el-form-item>
+        <el-form-item label="磷" prop="phosphorus">
+          <el-input v-model="form.phosphorus" placeholder="请输入磷" />
+        </el-form-item>
+        <el-form-item label="膳食纤维" prop="dietaryFiber">
+          <el-input v-model="form.dietaryFiber" placeholder="请输入膳食纤维" />
+        </el-form-item>
+        <el-form-item label="维生素B1" prop="vitaminB1">
+          <el-input v-model="form.vitaminB1" placeholder="请输入维生素B1" />
+        </el-form-item>
+        <el-form-item label="烟酸" prop="niacin">
+          <el-input v-model="form.niacin" placeholder="请输入烟酸" />
+        </el-form-item>
+        <el-form-item label="维生素E" prop="vitaminE">
+          <el-input v-model="form.vitaminE" placeholder="请输入维生素E" />
+        </el-form-item>
+        <el-form-item label="钙" prop="calcium">
+          <el-input v-model="form.calcium" placeholder="请输入钙" />
+        </el-form-item>
+        <el-form-item label="钾" prop="potassium">
+          <el-input v-model="form.potassium" placeholder="请输入钾" />
+        </el-form-item>
+        <el-form-item label="胆固醇" prop="cholesterol">
+          <el-input v-model="form.cholesterol" placeholder="请输入胆固醇" />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+        <!-- 上传excel -->
+    <el-dialog :title="dialogFile.title" v-model="dialogFile.visible" width="700px" append-to-body>
+      <el-upload  drag :action="uploadFileUrl" :headers="headers" :file-list="fileList"
+        accept=".xlsx,.xls"        
+        :on-success="handleUploadSuccess"
+      :before-upload="handleBeforeUpload"
+      :on-error="handleUploadError"      
+        >
+          <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+          <div class="el-upload__text">
+            将文件拖到此处,或 <em>点击上传</em>
+          </div>          
+        </el-upload>
+        <div class="el-upload__tip">
+            请上传 .xls , .xlsx 标准格式文件
+            </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="FoodIngredient" lang="ts">
+import { listFoodIngredient, getFoodIngredient, delFoodIngredient, addFoodIngredient, updateFoodIngredient } from '@/api/system/foodIngredient';
+import { FoodIngredientVO, FoodIngredientQuery, FoodIngredientForm } from '@/api/system/foodIngredient/types';
+import { listTree } from '@/api/system/foodCategory';
+import { FoodCategoryVO } from '@/api/system/foodCategory/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+import { UploadFilled } from '@element-plus/icons-vue'
+import { globalHeaders } from '@/utils/request';
+
+const router = useRouter();
+const foodIngredientList = ref<FoodIngredientVO[]>([]);
+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 foodCategoryList = ref<FoodCategoryVO[]>([]);
+
+const queryFormRef = ref<ElFormInstance>();
+const foodIngredientFormRef = ref<ElFormInstance>();
+
+const baseUrl = import.meta.env.VITE_APP_BASE_API;
+const uploadFileUrl = ref(baseUrl + '/system/foodIngredient/importExcel'); 
+const headers = ref(globalHeaders());
+const fileList = ref<any[]>([]);
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const dialogFile = reactive<DialogOption>({
+  visible: false,
+  title: '请上传'
+});
+
+const initFormData: FoodIngredientForm = {
+  foodIngredientId: undefined,
+  foodCategoryId: undefined,
+  name: undefined,
+  code: undefined,
+  unit: undefined,
+  purchasePrice: undefined,
+  edibleRatio: undefined,
+  shelfLife: undefined,
+  stockWarning: undefined,
+  expiryWarning: undefined,
+  description: undefined,
+  calories: undefined,
+  protein: undefined,
+  fat: undefined,
+  carbohydrate: undefined,
+  water: undefined,
+  vitaminA: undefined,
+  vitaminB2: undefined,
+  vitaminC: undefined,
+  sodium: undefined,
+  iron: undefined,
+  phosphorus: undefined,
+  dietaryFiber: undefined,
+  vitaminB1: undefined,
+  niacin: undefined,
+  vitaminE: undefined,
+  calcium: undefined,
+  potassium: undefined,
+  cholesterol: undefined,
+  status: undefined,
+}
+const data = reactive<PageData<FoodIngredientForm, FoodIngredientQuery>>({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    foodCategoryId: undefined,
+    name: undefined,
+    code: undefined,
+    unit: undefined,
+    purchasePrice: undefined,
+    edibleRatio: undefined,
+    shelfLife: undefined,
+    stockWarning: undefined,
+    expiryWarning: undefined,
+    description: undefined,
+    calories: undefined,
+    protein: undefined,
+    fat: undefined,
+    carbohydrate: undefined,
+    water: undefined,
+    vitaminA: undefined,
+    vitaminB2: undefined,
+    vitaminC: undefined,
+    sodium: undefined,
+    iron: undefined,
+    phosphorus: undefined,
+    dietaryFiber: undefined,
+    vitaminB1: undefined,
+    niacin: undefined,
+    vitaminE: undefined,
+    calcium: undefined,
+    potassium: undefined,
+    cholesterol: undefined,
+    status: undefined,
+    params: {
+    }
+  },
+  rules: {
+    foodIngredientId: [
+      { required: true, message: "主键ID不能为空", trigger: "blur" }
+    ],
+    foodCategoryId: [
+      { required: true, message: "食材分类ID不能为空", trigger: "blur" }
+    ],
+    name: [
+      { required: true, message: "食材名称不能为空", trigger: "blur" }
+    ],
+    code: [
+      { required: true, message: "食材编码不能为空", trigger: "blur" }
+    ],
+    unit: [
+      { required: true, message: "单位不能为空", trigger: "blur" }
+    ],
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询食材管理列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listFoodIngredient(queryParams.value);
+  foodIngredientList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  foodIngredientFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: FoodIngredientVO[]) => {
+  ids.value = selection.map(item => item.foodIngredientId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  router.push('/food/food-ingredient-page/0');
+}
+
+/** 修改按钮操作 */
+const handleUpdate = async (disabled:boolean,row?: FoodIngredientVO) => {
+  // reset();
+  const _foodIngredientId = row?.foodIngredientId || ids.value[0] 
+  if(disabled){
+    router.push('/food/food-ingredient-view/'+_foodIngredientId);
+  }else{
+    router.push('/food/food-ingredient-page/'+_foodIngredientId);
+  }  
+}
+
+/** 提交按钮 */
+const submitForm = () => {
+  foodIngredientFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.foodIngredientId) {
+        await updateFoodIngredient(form.value).finally(() =>  buttonLoading.value = false);
+      } else {
+        await addFoodIngredient(form.value).finally(() =>  buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功");
+      dialog.visible = false;
+      await getList();
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: FoodIngredientVO) => {
+  const _foodIngredientIds = row?.foodIngredientId || ids.value;
+  await proxy?.$modal.confirm('是否确认删除食材管理编号为"' + _foodIngredientIds + '"的数据项?').finally(() => loading.value = false);
+  await delFoodIngredient(_foodIngredientIds);
+  proxy?.$modal.msgSuccess("删除成功");
+  await getList();
+}
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download('system/foodIngredient/export', {
+    ...queryParams.value
+  }, `foodIngredient_${new Date().getTime()}.xlsx`)
+}
+
+/** 导出按钮操作 */
+const handleImport = () => {
+  dialogFile.visible = true;
+  fileList.value = [];
+}
+
+/** 导出按钮操作 */
+const downLoadTemplate = () => {
+  proxy?.getDownload('system/foodIngredient/downLoadTemplate', {
+  }, `食材模版.xlsx`)
+}
+
+
+// 上传失败
+const handleUploadError = () => {
+  proxy?.$modal.msgError('上传Excel失败');
+  proxy?.$modal.closeLoading();
+};
+
+// 上传成功回调
+const handleUploadSuccess = (res: any, file: UploadFile) => {
+  if (res.code === 200) {
+    dialogFile.visible = false;
+    fileList.value = [];
+    proxy?.$modal.closeLoading();
+    getList();
+  } else {
+    proxy?.$modal.msgError(res.msg);
+    proxy?.$modal.closeLoading();   
+  }
+  fileList.value = [];
+};
+
+
+/** 上传前loading加载 */
+const handleBeforeUpload = (file: any) => {
+   const isLt = file.size / 1024 / 1024 < 10;
+    if (!isLt) {
+      proxy?.$modal.msgError(`上传文件大小不能超过10MB!`);
+      return false;
+    }       
+  proxy?.$modal.loading('正在上传文件,请稍候...');
+  return true;
+};
+
+/** 查询食材分类列表 */
+const getList2 = async () => {   
+  const res = await listTree();  
+  foodCategoryList.value = res.rows;   
+}
+
+onMounted(() => {
+  getList();
+  getList2();
+});
+</script>

+ 1 - 1
src/views/system/role/index.vue

@@ -56,7 +56,7 @@
       </template>
 
       <el-table ref="roleTableRef" border stripe v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
-        <el-table-column type="selection" width="55" align="center" />
+        <!-- <el-table-column type="selection" width="55" align="center" /> -->
         <el-table-column v-if="false" label="角色编号" prop="roleId" width="120" />
         <el-table-column label="角色编码" prop="roleKey" :show-overflow-tooltip="true" width="200" />
         <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />

+ 1 - 1
src/views/system/user/index.vue

@@ -97,7 +97,7 @@
           </template>
 
           <el-table v-loading="loading" stripe border :data="userList" @selection-change="handleSelectionChange">
-            <el-table-column type="selection" width="50" align="center" />
+            <!-- <el-table-column type="selection" width="50" align="center" /> -->
             <!-- <el-table-column  key="userId" label="用户编号" align="center" prop="userId" /> -->
             <el-table-column key="name" label="姓名" align="center" prop="name" :show-overflow-tooltip="true" />
             <el-table-column   key="userName" label="登录名" align="center" prop="userName" :show-overflow-tooltip="true" width="130"/>

+ 1 - 1
src/views/system/ward/index.vue

@@ -39,7 +39,7 @@
       </template>
 
       <el-table v-loading="loading" border stripe :data="wardList" @selection-change="handleSelectionChange">
-        <el-table-column type="selection" width="55" align="center" />
+        <!-- <el-table-column type="selection" width="55" align="center" /> -->
         <!-- <el-table-column label="主键ID" align="center" prop="wardId" v-if="true" /> -->
         <el-table-column label="病区名称" align="center" prop="wardName"/>
         <el-table-column label="所属科室" align="center" prop="deptName" />         

+ 1 - 1
vite.config.ts

@@ -29,7 +29,7 @@ export default defineConfig(({ mode, command }) => {
       open: true,
       proxy: {
         [env.VITE_APP_BASE_API]: {
-          target: 'http://localhost:8080',
+          target: 'http://localhost:8081',
           changeOrigin: true,
           ws: true,
           rewrite: (path) => path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')