Bläddra i källkod

Merge branch 'dev-cy/end250630'

chenying2100 1 månad sedan
förälder
incheckning
42f34d7808

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

@@ -2678,7 +2678,6 @@ CREATE TABLE sys_recipe_food_ingredient (
 
 ---
 
-<<<<<<< HEAD
 _**User**_
 
 帮我用样式画出span的边框
@@ -2937,5 +2936,3 @@ _**Assistant**_
 
 ---
 
-=======
->>>>>>> dev-cy/end250630

+ 0 - 135
info.txt

@@ -1,135 +0,0 @@
-食谱分类管理   
-  食谱分类名称: 
-
-  CREATE TABLE sys_recipe_category (
-    recipe_category_id     BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
-    tenant_id varchar(20) default '000000' null comment '租户编号',
-    name            VARCHAR(100) NOT NULL COMMENT '食谱分类名称',     
-    sort            INT DEFAULT 0 COMMENT '排序',
-    -- 系统字段
-    status CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
-    del_flag char default '0' null comment '删除标志(0代表存在 1代表删除)',
-    create_dept  bigint null comment '创建部门',
-    create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
-    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
-    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食谱分类管理表';
-
-食谱管理
-  基本膳食
-  餐次
-  食谱名称 
-  食谱分类: 
-  食谱价格: 
-  适用疾病: 
-  忌食病症: 
-  适用科室: 
-  智能推荐:
-  制作方式:  
-
-
-CREATE TABLE sys_recipe (
-    recipe_id         BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
-    tenant_id  varchar(20) default '000000' null comment '租户编号',
-    name              VARCHAR(100) NOT NULL COMMENT '食谱名称',
-    category_id       BIGINT NOT NULL COMMENT '食谱分类ID',
-    base_diet         VARCHAR(100) DEFAULT NULL COMMENT '基本膳食',
-    meal_time         VARCHAR(100) DEFAULT NULL COMMENT '餐次',
-    price             DECIMAL(10,2) DEFAULT 0.00 COMMENT '食谱价格',
-    suitable_disease  VARCHAR(255) DEFAULT NULL COMMENT '适用疾病(可用逗号分隔多个)',
-    avoid_disease     VARCHAR(255) DEFAULT NULL COMMENT '忌食病症(可用逗号分隔多个)',
-    suitable_dept     VARCHAR(255) DEFAULT NULL COMMENT '适用科室(可用逗号分隔多个)',
-    smart_recommend   char(1) DEFAULT 0 COMMENT '智能推荐(0否 1是)',
-    cooking_method    VARCHAR(255) DEFAULT NULL COMMENT '制作方式', 
-
-    -- 系统字段
-    status CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
-    del_flag char default '0' null comment '删除标志(0代表存在 1代表删除)',
-    create_dept  bigint null comment '创建部门',
-    create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
-    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
-    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食谱管理表';
-
- 
-
-CREATE TABLE sys_recipe_food_ingredient (
-    id                  BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
-    recipe_id           BIGINT NOT NULL COMMENT '食谱ID',
-    food_ingredient_id  BIGINT NOT NULL COMMENT '食材ID',
-    quantity            DECIMAL(10,2) DEFAULT 0.00 COMMENT '用量(可选,单位自定义)',     
-    calories            DECIMAL(10,2) DEFAULT 0.00 COMMENT '热量(kcal)',
-    sort                INT DEFAULT 0 COMMENT '排序(可选)',    
-    remark              VARCHAR(255) DEFAULT NULL COMMENT '备注',
-    
-    -- 系统字段
-    status CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
-    del_flag char default '0' null comment '删除标志(0代表存在 1代表删除)',
-    create_dept  bigint null comment '创建部门',
-    create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
-    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
-    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食谱与食材关联表';
-
--- 食材管理表
-CREATE TABLE sys_food_ingredient (
-    food_ingredient_id     BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
-    food_category_id       BIGINT NOT NULL COMMENT '食材分类ID',
-    tenant_id varchar(20) default '000000' null comment '租户编号',
-    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 CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
-    del_flag char default '0' null comment '删除标志(0代表存在 1代表删除)',
-    create_dept  bigint null comment '创建部门',
-    create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
-    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-    update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
-    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='食材管理表';
- 
-
-
-
-
-          
-
-
-
-
-
-
-
-
-

+ 140 - 0
prompt/check-template.txt

@@ -0,0 +1,140 @@
+# 营养表单设计UI草图
+
+## 1. 页面整体布局
+- 顶部:页面标题(可选)
+- 左侧:参数选择区(系统参数、题型选择)
+- 右侧:表单编辑区(量表类型、适用条件、量表名称、说明、备注等)
+- 下方:题目编辑区(题目内容、题型、填空项等)
+
+---
+
+## 2. 结构化UI设计
+
+```
+<template>
+  <div class="check-template-container">
+    <!-- 左侧参数选择区 -->
+    <aside class="sidebar">
+      <section class="system-params">
+        <h3>系统参数</h3>
+        <div class="param-group">
+          <div class="group-title">基本信息</div>
+          <el-button>性别</el-button>
+          <el-button>出生日期</el-button>
+          <el-button>身高</el-button>
+          <el-button>体重</el-button>
+          <el-button>BMI</el-button>
+          <el-button>营养诊断</el-button>
+          <el-button>入院日期</el-button>
+        </div>
+        <div class="param-group">
+          <div class="group-title">其他</div>
+          <el-button>人体测量</el-button>
+          <el-button>膳食状况</el-button>
+          <el-button>营养生化检查</el-button>
+        </div>
+      </section>
+      <section class="question-types">
+        <h3>题型选择</h3>
+        <el-button>单选题</el-button>
+        <el-button>多选题</el-button>
+        <el-button>填空题</el-button>
+        <el-button>量表题</el-button>
+        <el-button>矩阵量表</el-button>
+      </section>
+    </aside>
+
+    <!-- 右侧表单编辑区 -->
+    <main class="main-content">
+      <el-form label-width="100px">
+        <el-form-item label="量表类型:" required>
+          <el-radio-group v-model="form.type">
+            <el-radio label="营养筛查" />
+            <el-radio label="营养评估" />
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="适用条件:">
+          <el-row>
+            <el-col :span="12">
+              <span>性别:</span><el-link type="primary">不限</el-link>
+            </el-col>
+            <el-col :span="12">
+              <span>年龄:</span><el-link type="primary">不限</el-link>
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item label="量表名称:" required>
+          <el-input v-model="form.name" maxlength="50" show-word-limit placeholder="请输入" />
+        </el-form-item>
+        <el-form-item label="量表说明:">
+          <el-input v-model="form.desc" maxlength="50" show-word-limit placeholder="请输入" />
+        </el-form-item>
+        <el-form-item label="备注:">
+          <el-input type="textarea" v-model="form.remark" maxlength="500" show-word-limit placeholder="请输入" />
+        </el-form-item>
+      </el-form>
+
+      <!-- 题目编辑区 -->
+      <section class="question-edit">
+        <el-card>
+          <el-form label-width="80px">
+            <el-form-item label="标题:" required>
+              <el-input v-model="question.title" placeholder="请输入标题" />
+            </el-form-item>
+            <el-form-item label="填空:">
+              <el-input v-model="question.blank" placeholder="请输入填空内容" />
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </section>
+    </main>
+  </div>
+</template>
+```
+
+---
+
+## 3. 样式建议(伪代码)
+
+```
+.check-template-container {
+  display: flex;
+  background: #f8fafc;
+}
+.sidebar {
+  width: 260px;
+  background: #fff;
+  border-radius: 4px;
+  margin: 16px;
+  padding: 16px;
+  box-shadow: 0 2px 8px #f0f1f2;
+}
+.main-content {
+  flex: 1;
+  margin: 16px;
+  background: #fff;
+  border-radius: 4px;
+  padding: 24px;
+  min-width: 600px;
+}
+.question-edit {
+  margin-top: 24px;
+  background: #f0f6ff;
+  border-radius: 4px;
+  padding: 16px;
+}
+```
+
+---
+
+## 4. 交互说明
+- 左侧参数和题型按钮可拖拽或点击添加到右侧表单或题目区。
+- 右侧表单区支持表单校验、字数限制、必填项提示。
+- 题目编辑区可根据题型动态切换输入控件。
+
+---
+
+# 设计说明
+- 结构清晰,左右分栏,主次分明。
+- 采用 Element Plus 组件,风格简洁现代。
+- 适合表单设计、量表配置等场景。 

+ 63 - 0
prompt/info.txt

@@ -0,0 +1,63 @@
+筛查/评估配置   
+  量表类型: 
+  适用条件: 
+  性别: 
+  年龄:
+  量表名称:
+  量表说明:
+  备注:
+
+CREATE TABLE sys_screening_assessment_config (
+    config_id     BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
+    tenant_id  varchar(20) default '000000' null comment '租户编号',
+    type          char(1) NOT NULL COMMENT '量表类型',           -- 如:营养筛查、营养评估
+    gender        char(2) DEFAULT '-1' COMMENT '性别',         -- 男、女、不限
+    age           char(2) DEFAULT '-1' COMMENT '年龄',         -- 如:不限、18-65、>65等
+    name          VARCHAR(150) NOT NULL COMMENT '量表名称',
+    description   VARCHAR(300) DEFAULT NULL COMMENT '量表说明',
+    remark        text DEFAULT NULL COMMENT '备注',
+     
+    -- 系统字段
+    status CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
+    del_flag char default '0' null comment '删除标志(0代表存在 1代表删除)',
+    create_dept  bigint null comment '创建部门',
+    create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
+    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
+    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='筛查/评估配置表';
+
+ 
+CREATE TABLE sys_screening_assessment_question(
+  question_id     BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
+  tenant_id  varchar(20) default '000000' null comment '租户编号',
+  config_id     BIGINT  COMMENT '主键ID',
+  title     VARCHAR(600) NOT NULL COMMENT '标题',
+  question_type VARCHAR(50) NOT NULL COMMENT '题型',
+  content   text COMMENT '内容',
+
+  -- 系统字段
+  create_dept  bigint null comment '创建部门',
+  create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
+  create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
+  update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='题目表';
+ 
+ 
+ 
+
+
+
+
+          
+
+
+
+
+
+
+
+ 
+
+

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

@@ -157,6 +157,19 @@ export interface FoodIngredientVO {
 
   quantity: number;
 
+  bigCategory: string;
+
+  subCategory: string;
+
+  nutrientName: string;
+
+  nutrientQuantity: number;
+
+  nutrientRatio: string;
+
+  nutrientRefVal: string;
+  
+  nutrientUnit: string;
 }
 
 export interface FoodIngredientForm extends BaseEntity {

+ 75 - 0
src/api/system/screeningAssessmentConfig/index.ts

@@ -0,0 +1,75 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ScreeningAssessmentConfigVO, ScreeningAssessmentConfigForm, ScreeningAssessmentConfigQuery } from '@/api/system/screeningAssessmentConfig/types';
+
+/**
+ * 查询筛查/评估配置列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listScreeningAssessmentConfig = (query?: ScreeningAssessmentConfigQuery): AxiosPromise<ScreeningAssessmentConfigVO[]> => {
+  return request({
+    url: '/system/screeningAssessmentConfig/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询筛查/评估配置详细
+ * @param configId
+ */
+export const getScreeningAssessmentConfig = (configId: string | number): AxiosPromise<ScreeningAssessmentConfigVO> => {
+  return request({
+    url: '/system/screeningAssessmentConfig/' + configId,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增筛查/评估配置
+ * @param data
+ */
+export const addScreeningAssessmentConfig = (data: ScreeningAssessmentConfigForm) => {
+  return request({
+    url: '/system/screeningAssessmentConfig',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改筛查/评估配置
+ * @param data
+ */
+export const updateScreeningAssessmentConfig = (data: ScreeningAssessmentConfigForm) => {
+  return request({
+    url: '/system/screeningAssessmentConfig',
+    method: 'put',
+    data: data
+  });
+};
+
+export const changeConfigStatus = (configId: string | number, status: string) => {
+  const data = {
+    configId,
+    status
+  };
+  return request({
+    url: '/system/screeningAssessmentConfig/changeStatus',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除筛查/评估配置
+ * @param configId
+ */
+export const delScreeningAssessmentConfig = (configId: string | number | Array<string | number>) => {
+  return request({
+    url: '/system/screeningAssessmentConfig/' + configId,
+    method: 'delete'
+  });
+};

+ 194 - 0
src/api/system/screeningAssessmentConfig/types.ts

@@ -0,0 +1,194 @@
+export interface ScreeningAssessmentConfigVO {
+  /**
+   * 主键ID
+   */
+  configId: string | number;
+
+  /**
+   * 量表类型
+   */
+  type: string;
+
+  /**
+   * 性别
+   */
+  gender: string;
+
+  /**
+   * 年龄
+   */
+  age: string;
+
+  /**
+   * 量表名称
+   */
+  name: string;
+
+  /**
+   * 量表说明
+   */
+  description: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+}
+
+export interface QuestionAttrVO {
+  labelFlag?: boolean;
+  label: string;
+  value?: string;
+  nameEn: string;
+  formType?: string;
+  score?: string;
+  img?: string;
+  allowFillBlank?: boolean;
+  unit?: string;
+  optionContent?: string;
+  optionFlag?: boolean;
+}
+
+export interface QuestionVO {
+  /**
+   * 主键ID
+   */
+  questionId: string | number;
+
+  /**
+   * 标题
+   */
+  title: string;
+
+  /**
+   * 题型
+   */
+  questionType: string;
+
+  /**
+   * 子题型
+   */
+  questionChildType?: string;
+
+  /**
+   * 系统参数-other类型:
+   */
+  lastType?: string;
+
+  /**
+   * 是否必填
+   */
+  required?: boolean;
+
+  /**
+   * 横向排列、竖向排列
+   */
+  arrangement?: string;
+
+  /**
+   * 累计选项得分、按最高分、按最低分
+   */
+  scoreMethod?: string;
+
+  /**
+   * 内容
+   */
+  content: string;
+
+  /**
+   * 是否被选择
+   */
+  selected?: boolean;
+
+  contentList: Array<QuestionAttrVO>;
+
+  increment?: number;
+}
+
+export interface ScreeningAssessmentConfigForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  configId?: string | number;
+
+  /**
+   * 量表类型
+   */
+  type?: string;
+
+  /**
+   * 性别
+   */
+  gender?: string;
+
+  /**
+   * 年龄
+   */
+  age?: string;
+
+  /**
+   * 量表名称
+   */
+  name?: string;
+
+  /**
+   * 量表说明
+   */
+  description?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  baseInfo: QuestionVO;
+
+  otherInfo: QuestionVO[];
+}
+
+export interface ScreeningAssessmentConfigQuery extends PageQuery {
+  /**
+   * 量表类型
+   */
+  type?: string;
+
+  /**
+   * 性别
+   */
+  gender?: string;
+
+  /**
+   * 年龄
+   */
+  age?: string;
+
+  /**
+   * 量表名称
+   */
+  name?: string;
+
+  /**
+   * 量表说明
+   */
+  description?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

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

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ScreeningAssessmentQuestionVO, ScreeningAssessmentQuestionForm, ScreeningAssessmentQuestionQuery } from '@/api/system/screeningAssessmentQuestion/types';
+
+/**
+ * 查询题目列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listScreeningAssessmentQuestion = (query?: ScreeningAssessmentQuestionQuery): AxiosPromise<ScreeningAssessmentQuestionVO[]> => {
+  return request({
+    url: '/system/screeningAssessmentQuestion/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询题目详细
+ * @param questionId
+ */
+export const getScreeningAssessmentQuestion = (questionId: string | number): AxiosPromise<ScreeningAssessmentQuestionVO> => {
+  return request({
+    url: '/system/screeningAssessmentQuestion/' + questionId,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增题目
+ * @param data
+ */
+export const addScreeningAssessmentQuestion = (data: ScreeningAssessmentQuestionForm) => {
+  return request({
+    url: '/system/screeningAssessmentQuestion',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改题目
+ * @param data
+ */
+export const updateScreeningAssessmentQuestion = (data: ScreeningAssessmentQuestionForm) => {
+  return request({
+    url: '/system/screeningAssessmentQuestion',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除题目
+ * @param questionId
+ */
+export const delScreeningAssessmentQuestion = (questionId: string | number | Array<string | number>) => {
+  return request({
+    url: '/system/screeningAssessmentQuestion/' + questionId,
+    method: 'delete'
+  });
+};

+ 86 - 0
src/api/system/screeningAssessmentQuestion/types.ts

@@ -0,0 +1,86 @@
+export interface ScreeningAssessmentQuestionVO {
+  /**
+   * 主键ID
+   */
+  questionId: string | number;
+
+  /**
+   * 主键ID
+   */
+  configId: string | number;
+
+  /**
+   * 标题
+   */
+  title: string;
+
+  /**
+   * 题型
+   */
+  questionType: string;
+
+  /**
+   * 内容
+   */
+  content: string;
+
+}
+
+export interface ScreeningAssessmentQuestionForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  questionId?: string | number;
+
+  /**
+   * 主键ID
+   */
+  configId?: string | number;
+
+  /**
+   * 标题
+   */
+  title?: string;
+
+  /**
+   * 题型
+   */
+  questionType?: string;
+
+  /**
+   * 内容
+   */
+  content?: string;
+
+}
+
+export interface ScreeningAssessmentQuestionQuery extends PageQuery {
+
+  /**
+   * 主键ID
+   */
+  configId?: string | number;
+
+  /**
+   * 标题
+   */
+  title?: string;
+
+  /**
+   * 题型
+   */
+  questionType?: string;
+
+  /**
+   * 内容
+   */
+  content?: string;
+
+    /**
+     * 日期范围参数
+     */
+    params?: any;
+}
+
+
+

+ 269 - 0
src/components/MiniImageUpload/index.vue

@@ -0,0 +1,269 @@
+<template>
+  <div class="component-upload-image" style="height: 40px;width: 40px;">
+    <el-upload
+      ref="imageUploadRef"
+      multiple
+      :action="uploadImgUrl"
+      list-type="picture-card"
+      :on-success="handleUploadSuccess"
+      :before-upload="handleBeforeUpload"
+      :limit="limit"
+      :disabled="disabled"
+      :accept="fileAccept"
+      :on-error="handleUploadError"
+      :on-exceed="handleExceed"
+      :before-remove="handleDelete"
+      :show-file-list="true"
+      :headers="headers"
+      :file-list="fileList"       
+      :class="{ hide: fileList.length >= limit }"
+    >
+      <el-icon class="avatar-uploader-icon" style="height: 10px;width: 10px;">
+        <plus/>
+      </el-icon>
+    </el-upload>
+    <!-- 上传提示 -->
+    <div v-if="showTip" class="el-upload__tip">
+      请上传
+      <template v-if="fileSize">
+        大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
+      </template>
+      <template v-if="fileType">
+        格式为 <b style="color: #f56c6c">{{ fileType.join('/') }}</b>
+      </template>
+      的文件
+    </div>
+
+    <!-- <el-dialog v-model="dialogVisible" title="预览" width="800px" append-to-body>
+      <img :src="dialogImageUrl" style="display: block; max-width: 100%; margin: 0 auto" />
+    </el-dialog> -->
+  </div>
+</template>
+
+<script setup lang="ts">
+import { listByIds, delOss } from '@/api/system/oss';
+import { OssVO } from '@/api/system/oss/types';
+import { propTypes } from '@/utils/propTypes';
+import { globalHeaders } from '@/utils/request';
+import { compressAccurately } from 'image-conversion';
+import { string } from 'vue-types';
+
+const props = defineProps({
+  modelValue: {
+    type: [String, Object, Array],
+    default: () => []
+  },
+  // 图片数量限制
+  limit: propTypes.number.def(5),
+  // 大小限制(MB)
+  fileSize: propTypes.number.def(5),
+  // 文件类型, 例如['png', 'jpg', 'jpeg']
+  fileType: propTypes.array.def(['png', 'jpg', 'jpeg']),
+  // 是否显示提示
+  isShowTip: {
+    type: Boolean,
+    default: false
+  },
+  disabled: {
+    type: Boolean,
+    default: false
+  },  
+  // 是否支持压缩,默认否
+  compressSupport: {
+    type: Boolean,
+    default: false
+  },
+   
+  // 压缩目标大小,单位KB。默认300KB以上文件才压缩,并压缩至300KB以内
+  compressTargetSize: propTypes.number.def(300)
+});
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const emit = defineEmits(['update:modelValue']);
+const number = ref(0);
+const uploadList = ref<any[]>([]);
+const dialogImageUrl = ref('');
+const dialogVisible = ref(false);
+
+const baseUrl = import.meta.env.VITE_APP_BASE_API;
+const uploadImgUrl = ref(baseUrl + '/api/qiniu/upload'); // 上传的图片服务器地址
+const headers = ref(globalHeaders());
+
+const fileList = ref<any[]>([]);
+const showTip = computed(() => props.isShowTip && (props.fileType || props.fileSize));
+
+const imageUploadRef = ref<ElUploadInstance>();
+
+// 监听 fileType 变化,更新 fileAccept
+const fileAccept = computed(() => props.fileType.map((type) => `.${type}`).join(','));
+
+watch(
+  () => props.modelValue,
+  async (val: string) => {
+    if (val) {
+      // 首先将值转为数组
+      let list: OssVO[] = [];
+      if (Array.isArray(val)) {
+        list = val as OssVO[];
+      } else {
+        const res = await listByIds(val);
+        list = res.data;
+      }
+      // 然后将数组转为对象数组
+      fileList.value = list.map((item) => {
+        // 字符串回显处理 如果此处存的是url可直接回显 如果存的是id需要调用接口查出来
+        let itemData;
+        if (typeof item === 'string') {
+          itemData = { name: item, url: item };
+        } else {
+          // 此处name使用ossId 防止删除出现重名
+          itemData = { name: item.ossId, url: item.url, ossId: item.ossId };
+        }
+        return itemData;
+      });
+    } else {
+      fileList.value = [];
+      return [];
+    }
+  },
+  { deep: true, immediate: true }
+);
+
+/** 上传前loading加载 */
+const handleBeforeUpload = (file: any) => {
+  let isImg = false;
+  if (props.fileType.length) {
+    let fileExtension = '';
+    if (file.name.lastIndexOf('.') > -1) {
+      fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1);
+    }
+    isImg = props.fileType.some((type: any) => {
+      if (file.type.indexOf(type) > -1) return true;
+      if (fileExtension && fileExtension.indexOf(type) > -1) return true;
+      return false;
+    });
+  } else {
+    isImg = file.type.indexOf('image') > -1;
+  }
+  if (!isImg) {
+    proxy?.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join('/')}图片格式文件!`);
+    return false;
+  }
+  if (file.name.includes(',')) {
+    proxy?.$modal.msgError('文件名不正确,不能包含英文逗号!');
+    return false;
+  }
+  if (props.fileSize) {
+    const isLt = file.size / 1024 / 1024 < props.fileSize;
+    if (!isLt) {
+      proxy?.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
+      return false;
+    }
+  }
+
+  //压缩图片,开启压缩并且大于指定的压缩大小时才压缩
+  if (props.compressSupport && file.size / 1024 > props.compressTargetSize) {
+    proxy?.$modal.loading('正在上传图片,请稍候...');
+    number.value++;
+    return compressAccurately(file, props.compressTargetSize);
+  } else {
+    proxy?.$modal.loading('正在上传图片,请稍候...');
+    number.value++;
+  }
+};
+
+// 文件个数超出
+const handleExceed = () => {
+  proxy?.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
+};
+
+// 上传成功回调
+const handleUploadSuccess = (res: any, file: UploadFile) => {
+  if (res.code === 200) {
+    uploadList.value.push({ name: res.data.fileName, url: res.data.url, ossId: res.data.ossId });
+    uploadedSuccessfully();
+  } else {
+    number.value--;
+    proxy?.$modal.closeLoading();
+    proxy?.$modal.msgError(res.msg);
+    imageUploadRef.value?.handleRemove(file);
+    uploadedSuccessfully();
+  }
+};
+
+// 删除图片
+const handleDelete = (file: UploadFile): boolean => {
+  const findex = fileList.value.map((f) => f.name).indexOf(file.name);
+  if (findex > -1 && uploadList.value.length === number.value) {
+    const ossId = fileList.value[findex].ossId;
+    delOss(ossId);
+    fileList.value.splice(findex, 1);
+    emit('update:modelValue', listToString(fileList.value));
+    return false;
+  }
+  return true;
+};
+
+// 上传结束处理
+const uploadedSuccessfully = () => {
+  if (number.value > 0 && uploadList.value.length === number.value) {
+    fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value);
+    uploadList.value = [];
+    number.value = 0;
+    emit('update:modelValue', listToString(fileList.value));
+    proxy?.$modal.closeLoading();
+  }
+};
+
+// 上传失败
+const handleUploadError = () => {
+  proxy?.$modal.msgError('上传图片失败');
+  proxy?.$modal.closeLoading();
+};
+
+// 预览
+const handlePictureCardPreview = (file: any) => {
+  dialogImageUrl.value = file.url;
+  dialogVisible.value = true;
+};
+
+// 对象转成指定字符串分隔
+const listToString = (list: any[], separator?: string) => {
+  let strs = '';
+  separator = separator || ',';
+  for (const i in list) {
+    if (undefined !== list[i].ossId && list[i].url.indexOf('blob:') !== 0) {
+      strs += list[i].ossId + separator;
+    }
+  }
+  return strs != '' ? strs.substring(0, strs.length - 1) : '';
+};
+</script>
+
+<style lang="scss" scoped>
+// .el-upload--picture-card 控制加号部分
+:deep(.hide .el-upload--picture-card) {
+  display: none;
+}
+:deep(.el-upload--picture-card) {
+  width: 40px !important;
+  height: 40px !important;
+  line-height: 40px !important;
+}
+:deep(.el-upload-list__item) {
+  width: 40px !important;
+  height: 40px !important;
+  line-height: 40px !important;
+}
+:deep(.el-upload-list__item-delete) {
+  width: 40px !important;
+  height: 40px !important;
+  line-height: 40px !important;
+  margin-right: 16px;   
+}
+:deep(.el-upload-list__item-preview) {
+  visibility: hidden;
+}
+
+
+</style>

+ 1 - 0
src/types/global.d.ts

@@ -60,6 +60,7 @@ declare global {
    */
   declare interface DictDataOption {
     label: string;
+    remark: string;
     value: string;
     elTagType?: ElTagType;
     elTagClass?: string;

+ 1 - 1
src/utils/dict.ts

@@ -16,7 +16,7 @@ export const useDict = (...args: string[]): { [key: string]: DictDataOption[] }
     } else {
       await getDicts(dictType).then((resp) => {
         res.value[dictType] = resp.data.map(
-          (p): DictDataOption => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass })
+          (p): DictDataOption => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass,remark: p.remark})
         );
         useDictStore().setDict(dictType, res.value[dictType]);
       });

+ 274 - 169
src/views/system/recipe/add.vue

@@ -20,7 +20,7 @@
               <el-form-item label="基本膳食" prop="baseDiet">
                 <el-radio-group v-model="form.baseDiet" :disabled="isView">
                   <el-radio v-for="item in dietary_types" :key="item.value" :label="item.value">{{ item.label
-                    }}</el-radio>
+                  }}</el-radio>
                 </el-radio-group>
               </el-form-item>
             </el-col>
@@ -152,8 +152,8 @@
         <el-divider />
         <div style="font-size: 14px;margin-top: -10px;margin-bottom: -10px;">食材组成</div>
         <el-divider />
-        <div style="margin-top: 10px; margin-bottom: 20px;"><span>食材总量:{{quantitySum}} g</span><span
-            style="margin-left: 100px;">膳食总热量:{{caloriesSum}}kcal</span></div>
+        <div style="margin-top: 10px; margin-bottom: 20px;"><span>食材总量:{{ quantitySum }} g</span><span
+            style="margin-left: 100px;">膳食总热量:{{ caloriesSum }}kcal</span></div>
 
 
         <el-table border :data="selectedIngredientListBak">
@@ -163,7 +163,7 @@
             <template #default="scope">
               <el-input v-model="scope.row.quantity" placeholder="请输入" :disabled="isView"
                 @input="editFoodquantity(scope.row)">
-                <template #append><span>{{scope.row.unitName}}</span></template>
+                <template #append><span>{{ scope.row.unitName }}</span></template>
               </el-input>
             </template>
           </el-table-column>
@@ -344,7 +344,7 @@
             <el-table-column label="疾病/部位名称" align="center" prop="labelName" />
             <el-table-column label="疾病/部位编码" align="center" prop="labelCode" />
             <el-table-column label="所属分类" align="center" prop="categoryName" />
-          </el-table>
+        </el-table>
         </div>
         <div style="margin-left: 10px;width: 100%;">
           <pagination v-show="total > -1" :total="total" v-model:page="diseaseLabelQuery.pageNum"
@@ -358,14 +358,14 @@
             <el-table-column label="疾病/部位名称" align="center" prop="labelName" />
             <el-table-column label="疾病/部位编码" align="center" prop="labelCode" />
             <el-table-column label="所属分类" align="center" prop="categoryName" />
-            <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
-              <template #default="scope">
-                <el-tooltip content="删除" placement="top">
+          <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="150">
+            <template #default="scope">
+              <el-tooltip content="删除" placement="top">
                   <el-button link type="danger" icon="Delete" @click="handleSelectedDelete2(scope.row)">删除</el-button>
-                </el-tooltip>
-              </template>
-            </el-table-column>
-          </el-table>
+              </el-tooltip>
+            </template>
+          </el-table-column>
+        </el-table>
         </div>
         <div style="margin-left: 10px;margin-top:36px;width: 100%;text-align: left;">
           <span style="color: rgb(118, 180, 244);">已选:{{ selecteddiseaseLabelList2.length }}项</span>
@@ -472,50 +472,53 @@
 
 
   <!-- 营养数据分析 -->
-  <el-dialog :title="dialog4.title" v-model="dialog4.visible" width="70%" append-to-body  >
+  <el-dialog :title="dialog4.title" v-model="dialog4.visible" width="70%" append-to-body>
     <div class="p-2">
       <el-menu class="el-menu-demo" mode="horizontal" @select="handleMenumSelect" :default-active="currentIndex">
         <el-menu-item index="1">三大营养素分析</el-menu-item>
         <el-menu-item index="2">营养数据分析</el-menu-item>
       </el-menu>
-    </div> 
+    </div>
 
-    <el-row :gutter="10" class="mb8" v-show="leftIndex==currentIndex">
+    <el-row :gutter="10" class="mb8" v-show="leftIndex == currentIndex">
       <el-col :span="14">
-        <div style="margin-top: 20px;margin-bottom: 20px;">实际热量:26.90kcal/d</div>
+        <div style="margin-top: 20px;margin-bottom: 20px;">实际热量:{{ leftCalories }}kcal/d</div>
         <div style="height: 300px;">
-          <el-table border stripe :data="diseaseLabelList" @select-all="handleSelectAllChange"
-            @select="handleSelectChange" ref="diseaseLabelRef" @selection-change="handleSelectionChange">             
-            <el-table-column label="三大营养素" align="center" prop="labelName" />
-            <el-table-column label="质量(g)" align="center" prop="labelCode" />
-            <el-table-column label="热量占比" align="center" prop="categoryName" />
-            <el-table-column label="参考值" align="center"/>             
+          <el-table border stripe :data="leftList">
+            <el-table-column label="三大营养素" align="center" prop="nutrientName" />
+            <el-table-column label="质量(g)" align="center" prop="nutrientQuantity" >
+              <template #default="scope">
+                {{ scope.row.nutrientQuantity.toFixed(4)}}
+              </template>
+            </el-table-column>
+            <el-table-column label="热量占比" align="center" prop="nutrientRatio" />
+            <el-table-column label="参考值" align="center" prop="nutrientRefVal" />
           </el-table>
-        </div>         
+        </div>
       </el-col>
       <el-col :span="10" align="center">
         <div style="height: 300px;">
           <v-chart :option="pieOption" style="width: 400px; height: 200px;" class="chart-center" />
-        </div>         
+        </div>
       </el-col>
     </el-row>
 
-    <el-row :gutter="10" class="mb8" v-show="rightIndex==currentIndex">
+    <el-row :gutter="10" class="mb8" v-show="rightIndex == currentIndex">
       <el-col :span="24">
         <div style="height: 400px;overflow-y: scroll;">
-          <el-table border stripe :data="diseaseLabelList" @select-all="handleSelectAllChange"
-            @select="handleSelectChange" ref="diseaseLabelRef" @selection-change="handleSelectionChange">
-            <el-table-column label="元素名称" align="center" prop="labelName" />
-            <el-table-column label="单位" align="center" prop="labelCode" />
-            <el-table-column label="食谱营养素含量" align="center" prop="categoryName" />
+          <el-table border stripe :data="rightList">
+            <el-table-column label="元素名称" align="center" prop="nutrientName" />
+            <el-table-column label="单位" align="center" prop="nutrientUnit" />
+            <el-table-column label="食谱营养素含量" align="center" prop="nutrientQuantity" />
           </el-table>
-        </div>         
-      </el-col>       
+        </div>
+      </el-col>
     </el-row>
 
     <el-row :gutter="10" class="mb8">
       <el-col :span="24" align="center">
-        <el-button type="primary" @click="handleSelectedconfirm">&nbsp;&nbsp;&nbsp;确定&nbsp;&nbsp;&nbsp;</el-button>         
+        <el-button type="primary"
+          @click="dialog4.visible = !dialog4.visible">&nbsp;&nbsp;&nbsp;确定&nbsp;&nbsp;&nbsp;</el-button>
       </el-col>
     </el-row>
   </el-dialog>
@@ -526,20 +529,20 @@ import { listTree } from '@/api/system/foodCategory';
 import { FoodCategoryVO } from '@/api/system/foodCategory/types';
 import { listDiseaseLabel, getDiseaseLabel, delDiseaseLabel, addDiseaseLabel, updateDiseaseLabel } from '@/api/system/diseaseLabel';
 import { DiseaseLabelVO, DiseaseLabelQuery, DiseaseLabelForm } from '@/api/system/diseaseLabel/types';
-import { listRecipe, getRecipe, delRecipe, addRecipe, updateRecipe,nutrientAnalysis } from '@/api/system/recipe';
+import { listRecipe, getRecipe, delRecipe, addRecipe, updateRecipe, nutrientAnalysis } from '@/api/system/recipe';
 import { RecipeVO, RecipeQuery, RecipeForm } from '@/api/system/recipe/types';
 import { listRecipeCategory, getRecipeCategory, delRecipeCategory, addRecipeCategory, updateRecipeCategory } from '@/api/system/recipeCategory';
 import { RecipeCategoryVO, RecipeCategoryQuery, RecipeCategoryForm } from '@/api/system/recipeCategory/types';
 import { DeptTreeVO } from '@/api/system/dept/types';
 import api from '@/api/system/user';
-import { FoodIngredientVO,FoodIngredientQuery } from '@/api/system/foodIngredient/types';
+import { FoodIngredientVO, FoodIngredientQuery } from '@/api/system/foodIngredient/types';
 import { listFoodIngredient, getFoodIngredient, delFoodIngredient, addFoodIngredient, updateFoodIngredient } from '@/api/system/foodIngredient';
- 
-  
+
+
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { intelligent_recommendation, meal_times, dietary_types, disease_label } = toRefs<any>(
-  proxy?.useDict('intelligent_recommendation', 'meal_times', 'dietary_types', 'disease_label'));
+const { intelligent_recommendation, meal_times, dietary_types, disease_label, nutrient_reference } = toRefs<any>(
+  proxy?.useDict('intelligent_recommendation', 'meal_times', 'dietary_types', 'disease_label', 'nutrient_reference'));
 
 const foodCategoryList = ref<FoodCategoryVO[]>([]);
 const diseaseLabelFormRef = ref<ElFormInstance>();
@@ -564,25 +567,31 @@ const foodIngredientList = ref<FoodIngredientVO[]>([]);
 const selectedIngredientList = ref<FoodIngredientVO[]>([]);
 const selectedIngredientListBak = ref<FoodIngredientVO[]>([]);
 const foodIngredienRef = ref<ElTableInstance>();
-   
+
+const leftList = ref<FoodIngredientVO[]>([]);
+const leftCalories = ref<number>(0.0);
+const rightList = ref<FoodIngredientVO[]>([]);
+
 const caloriesSum = ref('0.0000');
 const quantitySum = ref('0.00');
 
 const leftIndex = ref('1')
-const rightIndex  = ref('2')
-const currentIndex  = ref('1')
+const rightIndex = ref('2')
+const currentIndex = ref('1')
+
+
 const pieOption = ref({
   legend: {
     orient: 'horizontal',
-    bottom:'bottom',
-    data: ['蛋白质 30%', '脂肪 20%', '碳水化合物 50%']    
+    bottom: 'bottom',
+    data: ['蛋白质 30%', '脂肪 20%', '碳水化合物 50%']
   },
-  title:{
-    text:'三大营养素元素质量占比',     
-    left:'center'    
+  title: {
+    text: '三大营养素元素质量占比',
+    left: 'center'
   },
   series: [
-    {      
+    {
       type: 'pie',
       radius: ['50%', '70%'],
       avoidLabelOverlap: false,
@@ -603,7 +612,7 @@ const pieOption = ref({
       data: [
         { value: 30, name: '蛋白质 30%' },
         { value: 20, name: '脂肪 20%' },
-        { value: 50, name: '碳水化合物 50%' },        
+        { value: 50, name: '碳水化合物 50%' },
       ]
     }
   ]
@@ -747,17 +756,17 @@ const data = reactive<PageData<RecipeForm, RecipeQuery>>({
     ],
     price: [
       { required: true, message: "食谱价格不能为空", trigger: "blur" }
-    ],    
+    ],
   }
 });
 
 const { queryParams, form, rules } = toRefs(data);
 
 /** 查询食材分类列表 */
-const getListTree = async () => {   
-  const res = await listTree();  
+const getListTree = async () => {
+  const res = await listTree();
   foodCategoryList.value = res.rows;
-   
+
 }
 
 /** 关闭按钮 */
@@ -893,7 +902,7 @@ const handleSelectChange2 = (selection: DiseaseLabelVO[], row: DiseaseLabelVO) =
     selecteddiseaseLabelList2.value = tList.value;
   }
 }
- 
+
 
 /** 多选框选中数据 */
 const handleSelectAllChange3 = (selection: FoodIngredientVO[]) => {
@@ -928,7 +937,7 @@ const handleSelectChange3 = (selection: FoodIngredientVO[], row: FoodIngredientV
     selectedIngredientList.value.push(row);
   } else {
     let tList = ref<FoodIngredientVO[]>([]);
-      selectedIngredientList.value.forEach(item => {
+    selectedIngredientList.value.forEach(item => {
       if (item.foodIngredientId != row.foodIngredientId) {
         tList.value.push(item);
       }
@@ -969,7 +978,7 @@ const handleSelectedDelete2 = (row?: DiseaseLabelVO[]) => {
   }
 }
 
- 
+
 
 /** 查询食谱管理列表 */
 const getRecipeCategoryList = async () => {
@@ -1054,10 +1063,10 @@ const handleSelectedconfirm2 = async () => {
 }
 
 const handleSelectedconfirm3 = async () => {
-  dialog3.visible = false;   
-  selectedIngredientListBak.value = selectedIngredientList.value; 
-  selectedIngredientList.value=[];  
-  foodIngredienRef.value?.clearSelection();  
+  dialog3.visible = false;
+  selectedIngredientListBak.value = selectedIngredientList.value;
+  selectedIngredientList.value = [];
+  foodIngredienRef.value?.clearSelection();
 }
 
 const addDiseaseLabelDialog = async () => {
@@ -1162,40 +1171,40 @@ const diseaseLabelSubmitForm2 = async () => {
 
 /** 查询食材管理列表 */
 const getListFoodIngredient = async () => {
-  const res = await listFoodIngredient(foodIngredientQuery.value);  
+  const res = await listFoodIngredient(foodIngredientQuery.value);
   foodIngredientList.value = res.rows;
-  totalFood.value = res.total; 
-  if (res.rows&&res.rows.length>0&&selectedIngredientList.value&&selectedIngredientList.value.length>0) {
+  totalFood.value = res.total;
+  if (res.rows && res.rows.length > 0 && selectedIngredientList.value && selectedIngredientList.value.length > 0) {
     setTimeout(() => {
       let tJson = ref({})
-    selectedIngredientList.value.forEach(item => {
-      tJson.value[item.foodIngredientId] = true;
-    })
-    
-    foodIngredientList.value.forEach(item => {
-      if (tJson.value[item.foodIngredientId]) {        
-        foodIngredienRef.value.toggleRowSelection(item, true);
-      }
-    })
-    }, 500);  
+      selectedIngredientList.value.forEach(item => {
+        tJson.value[item.foodIngredientId] = true;
+      })
+
+      foodIngredientList.value.forEach(item => {
+        if (tJson.value[item.foodIngredientId]) {
+          foodIngredienRef.value.toggleRowSelection(item, true);
+        }
+      })
+    }, 500);
   }
 }
 
 const handleNodeClick = async (data: FoodCategoryVO) => {
-  if(foodIngredientQuery.value.foodCategoryId!=data.foodCategoryId){
-    foodIngredientQuery.value.foodCategoryId=data.foodCategoryId;
-    await getListFoodIngredient(); 
+  if (foodIngredientQuery.value.foodCategoryId != data.foodCategoryId) {
+    foodIngredientQuery.value.foodCategoryId = data.foodCategoryId;
+    await getListFoodIngredient();
   }
 }
 
 const openAddFoodDialog = async () => {
   dialog3.visible = true;
   dialog3.title = '添加食材';
-  foodIngredientQuery.value.name=undefined;
-  foodIngredientQuery.value.foodCategoryId=undefined;
+  foodIngredientQuery.value.name = undefined;
+  foodIngredientQuery.value.foodCategoryId = undefined;
   await getListTree();
   await getListFoodIngredient();
- 
+
   if (selectedIngredientListBak.value && selectedIngredientListBak.value.length > 0) {
     let dataMap = ref({});
     selectedIngredientListBak.value.forEach(item => {
@@ -1212,41 +1221,41 @@ const openAddFoodDialog = async () => {
 }
 
 const editFoodquantity = async (data: FoodIngredientVO) => {
-   let quantity=Number(data.quantity)/100;
-   let calories=Number(data.calories); 
-   data.caloriesForInput=Math.floor(quantity*calories*100)/100;
-
-   let qSum=ref(0.0);
-   let cSum=ref(0.0);
-   selectedIngredientListBak.value.forEach(item=>{
-    if(item.caloriesForInput){
-      cSum.value+=item.caloriesForInput;
+  let quantity = Number(data.quantity) / 100;
+  let calories = Number(data.calories);
+  data.caloriesForInput = Math.floor(quantity * calories * 100) / 100;
+
+  let qSum = ref(0.0);
+  let cSum = ref(0.0);
+  selectedIngredientListBak.value.forEach(item => {
+    if (item.caloriesForInput) {
+      cSum.value += item.caloriesForInput;
     }
-    if(item.quantity){
-      qSum.value+=Number(item.quantity);
+    if (item.quantity) {
+      qSum.value += Number(item.quantity);
     }
-   });    
-   caloriesSum.value=(Math.floor(cSum.value*100)/100).toString();
-   quantitySum.value=(Math.floor(qSum.value*100)/100).toString();
+  });
+  caloriesSum.value = (Math.floor(cSum.value * 100) / 100).toString();
+  quantitySum.value = (Math.floor(qSum.value * 100) / 100).toString();
 }
 
 const deleteFoodquantity = async (data: FoodIngredientVO) => {
   let tList = ref<FoodIngredientVO[]>([]);
   selectedIngredientListBak.value.forEach(item => {
-    if(data.foodIngredientId!=item.foodIngredientId){
+    if (data.foodIngredientId != item.foodIngredientId) {
       tList.value.push(item);
     }
   });
-  selectedIngredientListBak.value= tList.value
-  selectedIngredientList.value=[];
-  foodIngredienRef.value?.clearSelection(); 
+  selectedIngredientListBak.value = tList.value
+  selectedIngredientList.value = [];
+  foodIngredienRef.value?.clearSelection();
 }
 
 /** 提交按钮 */
-const submitForm = () => {  
+const submitForm = () => {
   foodIngredientFormRef.value?.validate(async (valid: boolean) => {
     if (valid) {
-      if(!selectedIngredientListBak.value||selectedIngredientListBak.value.length==0){
+      if (!selectedIngredientListBak.value || selectedIngredientListBak.value.length == 0) {
         proxy?.$modal.msgError("请先添加【食材组成】部分的信息");
         return;
       }
@@ -1255,23 +1264,23 @@ const submitForm = () => {
       Object.assign(params.value, form.value);
       params.value.suitableDept = form.value.suitableDeptList.join(',')
       params.value.suitableDisease = selecteddiseaseLabelList.value.map(v => v.labelId).join(',')
-      params.value.avoidDisease = selecteddiseaseLabelList2.value.map(v => v.labelId).join(',')       
+      params.value.avoidDisease = selecteddiseaseLabelList2.value.map(v => v.labelId).join(',')
       params.value.mealTime = params.value.mealTimes.join(',')
-      params.value.foodList=[];
+      params.value.foodList = [];
 
-      for(let item of selectedIngredientListBak.value){
-        let dataJSon={};
-        dataJSon['foodIngredientId']=item.foodIngredientId;
-        dataJSon['quantity']=item.quantity;
-        if(!item.quantity){
+      for (let item of selectedIngredientListBak.value) {
+        let dataJSon = {};
+        dataJSon['foodIngredientId'] = item.foodIngredientId;
+        dataJSon['quantity'] = item.quantity;
+        if (!item.quantity) {
           buttonLoading.value = false;
           proxy?.$modal.msgError("【食材用量】不能为空");
           return;
         }
-        dataJSon['calories']=item.caloriesForInput;
-        dataJSon['remark']=item.remark;
+        dataJSon['calories'] = item.caloriesForInput;
+        dataJSon['remark'] = item.remark;
         params.value.foodList.push(dataJSon);
-      }       
+      }
 
       if (form.value.recipeId) {
         await updateRecipe(params.value).finally(() => buttonLoading.value = false);
@@ -1279,7 +1288,7 @@ const submitForm = () => {
         await addRecipe(params.value).then(res => {
           form.value.recipeId = res.data;
         }).finally(() => buttonLoading.value = false);
-      }      
+      }
       proxy?.$modal.msgSuccess("操作成功");
       close();
     }
@@ -1290,77 +1299,167 @@ const submitForm = () => {
 const getList = async () => {
   const recipeId = route.params && route.params.id;
   const vRecipeId = route.params && route.params.vid;
-  let id=undefined;
-  if(vRecipeId){
-    isView.value=true;
-    id=vRecipeId;
-  }else{
-    isView.value=false ;
-    id=recipeId;
+  let id = undefined;
+  if (vRecipeId) {
+    isView.value = true;
+    id = vRecipeId;
+      } else {
+    isView.value = false;
+    id = recipeId;
   }
-  
-  if (id&&id!='0') {
+
+  if (id && id != '0') {
     dataLoading.value = true;
     const res = await getRecipe(id as string);
     Object.assign(form.value, res.data);
-     
-    if(res.data.suitableDept&&res.data.suitableDept.length>0){
-      form.value.suitableDeptList=res.data.suitableDept.split(',');
-    } 
-    if(res.data.avoidDiseaseList&&res.data.avoidDiseaseList.length>0){
-      selecteddiseaseLabelListBak2.value=res.data.avoidDiseaseList;
-      selecteddiseaseLabelList2.value=res.data.avoidDiseaseList;
+
+    if (res.data.suitableDept && res.data.suitableDept.length > 0) {
+      form.value.suitableDeptList = res.data.suitableDept.split(',');
+    }
+    if (res.data.avoidDiseaseList && res.data.avoidDiseaseList.length > 0) {
+      selecteddiseaseLabelListBak2.value = res.data.avoidDiseaseList;
+      selecteddiseaseLabelList2.value = res.data.avoidDiseaseList;
       form.value.avoidDisease = selecteddiseaseLabelList2.value.map(v => v.labelName).join(',')
-    }  
-    if(res.data.suitableDiseaseList&&res.data.suitableDiseaseList.length>0){
-      selecteddiseaseLabelListBak.value=res.data.suitableDiseaseList;
-      selecteddiseaseLabelList.value=res.data.suitableDiseaseList;
+    }
+    if (res.data.suitableDiseaseList && res.data.suitableDiseaseList.length > 0) {
+      selecteddiseaseLabelListBak.value = res.data.suitableDiseaseList;
+      selecteddiseaseLabelList.value = res.data.suitableDiseaseList;
       form.value.suitableDisease = selecteddiseaseLabelList.value.map(v => v.labelName).join(',')
-    } 
-    if(res.data.mealTime&&res.data.mealTime.length>0){      
+    }
+    if (res.data.mealTime && res.data.mealTime.length > 0) {
       form.value.mealTimes = res.data.mealTime.split(',')
-    }     
-    if(res.data.foodList&&res.data.foodList.length>0){
-      selectedIngredientListBak.value=res.data.foodList;       
-      let qSum=ref(0.0);
-      let cSum=ref(0.0);
-      selectedIngredientListBak.value.forEach(item=>{
-        let quantity=Number(item.quantity)/100;
-        let calories=Number(item.calories);
-        item.caloriesForInput=Math.floor(quantity*calories*100)/100;
-
-        if(item.caloriesForInput){
-          cSum.value+=item.caloriesForInput;
+    }
+    if (res.data.foodList && res.data.foodList.length > 0) {
+      selectedIngredientListBak.value = res.data.foodList;
+      let qSum = ref(0.0);
+      let cSum = ref(0.0);
+      selectedIngredientListBak.value.forEach(item => {
+        let quantity = Number(item.quantity) / 100;
+        let calories = Number(item.calories);
+        item.caloriesForInput = Math.floor(quantity * calories * 100) / 100;
+
+        if (item.caloriesForInput) {
+          cSum.value += item.caloriesForInput;
         }
-        if(item.quantity){
-          qSum.value+=Number(item.quantity);
+        if (item.quantity) {
+          qSum.value += Number(item.quantity);
         }
       });
-      caloriesSum.value=(Math.floor(cSum.value*100)/100).toString();
-      quantitySum.value=(Math.floor(qSum.value*100)/100).toString();
-    }     
-    
+      caloriesSum.value = (Math.floor(cSum.value * 100) / 100).toString();
+      quantitySum.value = (Math.floor(qSum.value * 100) / 100).toString();
+    }
+
     dataLoading.value = false;
-  }  
+  }
 }
 
 const handleMenumSelect = async (key: string, keyPath: string[]) => {
-  currentIndex.value=key.toString()
+  currentIndex.value = key.toString()
+}
+
+const nutrientAnalysisLeftData = async () => {
+  let dataTemplate = JSON.stringify({
+    nutrientName: '',
+    nutrientQuantity: 0,
+    nutrientRatio: 0,
+    nutrientRefVal: ''
+  })
+
+  leftList.value=[];
+  let protein = ref<FoodIngredientVO>();
+  let fat = ref<FoodIngredientVO>();
+  let carbohydrate = ref<FoodIngredientVO>();
+  protein.value = JSON.parse(dataTemplate);
+  fat.value = JSON.parse(dataTemplate);
+  carbohydrate.value = JSON.parse(dataTemplate);
+  leftList.value.push(protein.value);
+  leftList.value.push(fat.value);
+  leftList.value.push(carbohydrate.value);
+
+  let nrJson = {};
+  nutrient_reference.value.forEach(nr => {
+    nrJson[nr.label] = nr;
+  })
+  protein.value.nutrientRefVal = nrJson['nutrient_protein'].value;
+  fat.value.nutrientRefVal = nrJson['nutrient_fat'].value;
+  carbohydrate.value.nutrientRefVal = nrJson['nutrient_carbohydrate'].value;
+  protein.value.nutrientName = '蛋白质';
+  fat.value.nutrientName = '脂肪';
+  carbohydrate.value.nutrientName = '碳水化合物';
+
+  let sum: number = 0.0; 
+
+  selectedIngredientListBak.value.forEach(item => {
+    protein.value.nutrientQuantity += Number(item.protein);
+    fat.value.nutrientQuantity += Number(item.fat);
+    carbohydrate.value.nutrientQuantity += Number(item.carbohydrate);
+    sum += protein.value.nutrientQuantity + fat.value.nutrientQuantity + carbohydrate.value.nutrientQuantity;
+    leftCalories.value += Number(item.caloriesForInput);
+  })  
+
+  protein.value.nutrientRatio = (sum > 0 ? (protein.value.nutrientQuantity / sum * 100).toFixed(2) : '0.00') + '%';
+  fat.value.nutrientRatio = (sum > 0 ? (fat.value.nutrientQuantity / sum * 100).toFixed(2) : '0.00') + '%';
+  carbohydrate.value.nutrientRatio = (sum > 0 ? (carbohydrate.value.nutrientQuantity / sum * 100).toFixed(2) : '0.00') + '%';
+ 
+  pieOption.value.legend.data = []
+  pieOption.value.series[0].data = []
+  leftList.value.forEach(item => {
+    pieOption.value.series[0].data.push({ value: Number(item.nutrientRatio.replace('%', '')), name: item.nutrientName + ' ' + item.nutrientRatio })
+    pieOption.value.legend.data.push(item.nutrientName + ' ' + item.nutrientRatio);
+  });
+
+  return nrJson['nutritional_data_analysis'].remark
 }
 
-const showNutrientAnalysisDialog =async ( ) => {
-  dialog4.visible=true;
-  dialog4.title='营养数据分析';   
+
+const nutrientAnalysisRightData = async (jsonStr:string) => {
+  rightList.value=[];
+  if(selectedIngredientListBak.value.length<1){
+    return; 
+  }
+  
+  let nutritionalDataAnalysis =  JSON.parse(jsonStr);
+  nutritionalDataAnalysis.forEach((na:any) => {
+    let showData={}
+    selectedIngredientListBak.value.forEach(item => {
+      let quantity = Number(item.quantity?item.quantity:0) / 100;
+      if(!showData['nutrientName']){
+        showData['nutrientUnit'] =na.unit;
+        showData['nutrientName'] = na.remark;
+        showData['nutrientQuantity'] = item[na.name]?Number(item[na.name])*quantity/100 : 0;
+      } else{
+        showData['nutrientQuantity'] = (item[na.name]?Number(item[na.name])*quantity/100:0)+showData['nutrientQuantity'];
+      }       
+  });
+   
+   rightList.value.push(JSON.parse(JSON.stringify(showData)));
+  }); 
+  
+  rightList.value.forEach(item=>{
+    if(item.nutrientQuantity>0){
+      item.nutrientQuantity=Math.floor(item.nutrientQuantity*10000)/10000
+    }
+  })
+  
 }
 
-// nutrientAnalysis
+const nutrientAnalysisData = async () => {
+  let jsonStr:string=await nutrientAnalysisLeftData();
+  await nutrientAnalysisRightData(jsonStr);  
+}
+
+const showNutrientAnalysisDialog = async () => {
+  dialog4.visible = true;
+  dialog4.title = '营养数据分析';
+  nutrientAnalysisData();
+}
 
 
 onMounted(() => {
   getDiseaseLabelList()
   getDeptTree();
-  getRecipeCategoryList(); 
-  getList();  
+  getRecipeCategoryList();
+  getList();
 });
 </script>
 
@@ -1377,10 +1476,10 @@ onMounted(() => {
 }
 
 .diagnosis-tag {
-  border-left: 1px solid #409EFF;
-  border-top: 1px solid #409EFF;
-  border-bottom: 1px solid #409EFF;
-  padding: 8px 8px;
+  border-left: 1px solid #409EFF;  
+  border-top: 1px solid #409EFF;  
+  border-bottom: 1px solid #409EFF;  
+  padding: 8px 8px;   
   color: #409EFF;
   background: #f4f8ff;
   display: inline-block;
@@ -1390,8 +1489,8 @@ onMounted(() => {
   vertical-align: middle;
 }
 
-.diagnosis-tag:last-child {
-  border-right: 1px solid #409EFF;
+.diagnosis-tag:last-child {  
+  border-right: 1px solid #409EFF; 
 }
 
 .diagnosisActive {
@@ -1402,12 +1501,18 @@ onMounted(() => {
 .el-pagination__sizes {
   width: 100px;
 }
+
 .chart-center {
   display: flex;
-  flex-direction: column;      /* 垂直方向排列 */
-  justify-content: center;     /* 垂直居中 */
-  align-items: center;         /* 水平居中 */
-  height: 100%;                /* 父容器需有高度 */
-  min-height: 300px;           /* 可根据需要设置 */
+  flex-direction: column;
+  /* 垂直方向排列 */
+  justify-content: center;
+  /* 垂直居中 */
+  align-items: center;
+  /* 水平居中 */
+  height: 100%;
+  /* 父容器需有高度 */
+  min-height: 300px;
+  /* 可根据需要设置 */
 }
 </style>

+ 1762 - 0
src/views/system/screeningAssessmentConfig/add.vue

@@ -0,0 +1,1762 @@
+<template>
+  <div class="p-2">
+    <!-- 新增顶部操作栏 -->
+    <el-row type="flex" align="middle" justify="space-between" style="margin-bottom: 16px;">
+      <el-col :span="4">
+        <el-button type="primary" plain @click="close" style="margin-right: 20px;margin-left: 30px;">返回</el-button>
+        <span style="font-size: 14px;  vertical-align: middle;">新增量表</span>
+      </el-col>
+      <el-col :span="4" style="text-align: right;">
+        <!-- <el-button type="primary" plain @click="handlePreview" style="margin-right: 8px;">预览</el-button> -->
+        <el-button type="primary" @click="submitForm" style="margin-right: 30px;">保存</el-button>
+      </el-col>
+    </el-row>
+
+    <el-container class="layout-container-demo" style="height: 100vh; min-height: 100vh;">
+      <el-aside id="leftPanel"
+        style="background:transparent; width: 450px; min-width: 300px; max-width: 500px; flex: 0 0 auto; border-right: 2px solid #e4e7ed;">
+        <div style="background:#f6f9fc; border-radius:4px; margin-bottom: 16px; padding: 16px 0;">
+          <div style="font-weight:bold; font-size:16px; padding:0 24px 8px 24px; cursor:pointer;"
+            @click="showSystemParam = !showSystemParam">
+            系统参数
+            <i class="el-icon-arrow-down" :style="{
+              float: 'right',
+              transition: 'transform 0.2s',
+              transform: showSystemParam ? 'rotate(0deg)' : 'rotate(-90deg)'
+            }"></i>
+          </div>
+          <div v-show="showSystemParam" style="padding: 16px 16px 0 16px;">
+            <div style="font-weight: bold; margin-bottom: 8px;">基本信息</div>
+            <el-row :gutter="20" style="margin-bottom: 24px;">
+              <el-col :span="8">
+                <el-button class="param-btn" plain @click="handleBaseBtnClick('gender', '性别')">性别</el-button>
+              </el-col>
+              <el-col :span="8">
+                <el-button class="param-btn" plain @click="handleBaseBtnClick('birthDate', '出生日期')">出生日期</el-button>
+              </el-col>
+              <el-col :span="8">
+                <el-button class="param-btn" plain @click="handleBaseBtnClick('height', '身高')">身高</el-button>
+              </el-col>
+              <el-col :span="8">
+                <el-button class="param-btn" plain @click="handleBaseBtnClick('weight', '体重')">体重</el-button>
+              </el-col>
+              <el-col :span="8">
+                <el-button class="param-btn" plain @click="handleBaseBtnClick('BMI', 'BMI')">BMI</el-button>
+              </el-col>
+              <el-col :span="8">
+                <el-button class="param-btn" plain
+                  @click="handleBaseBtnClick('nutritionalDiagnosis', '营养诊断')">营养诊断</el-button>
+              </el-col>
+              <el-col :span="8">
+                <el-button class="param-btn" plain @click="handleBaseBtnClick('admissionDate', '入院日期')">入院日期</el-button>
+              </el-col>
+            </el-row>
+            <div style="font-weight: bold; margin-bottom: 8px;">其他</div>
+            <el-row :gutter="20">
+              <el-col :span="8">
+                <el-button class="param-btn" plain
+                  @click="handleOtherBtnClick('otherInfo', 'anthropometry', '人体测量')">人体测量</el-button>
+              </el-col>
+              <el-col :span="8">
+                <el-button class="param-btn" plain
+                  @click="handleOtherBtnClick('otherInfo', 'dietaryStatus', '膳食状况')">膳食状况</el-button>
+              </el-col>
+              <el-col :span="8">
+                <el-button class="param-btn" plain
+                  @click="handleOtherBtnClick('otherInfo', 'nutritionalBiochemical', '营养生化检查')">营养生化检查</el-button>
+              </el-col>
+            </el-row>
+          </div>
+        </div>
+        <div style="background:#f6f9fc; border-radius:4px; margin-bottom: 16px; padding: 16px 0;">
+          <div style="font-weight:bold; font-size:16px; padding:0 24px 8px 24px; cursor:pointer;"
+            @click="showQuestionType = !showQuestionType">
+            题型选择
+            <i class="el-icon-arrow-down" :style="{
+              float: 'right',
+              transition: 'transform 0.2s',
+              transform: showQuestionType ? 'rotate(0deg)' : 'rotate(-90deg)'
+            }"></i>
+          </div>
+          <div v-show="showQuestionType" style="min-height:40px; padding:0 24px 16px 24px;">
+            <el-row :gutter="12">
+              <el-col :span="8"><el-button class="param-btn" plain
+                  @click="handleOtherBtnClick('questionTypeSelection', 'singleChoice', '单选题')">单选题</el-button></el-col>
+              <el-col :span="8"><el-button class="param-btn" plain
+                  @click="handleOtherBtnClick('questionTypeSelection', 'multipleChoice', '多选题')">多选题</el-button></el-col>
+              <el-col :span="8"><el-button class="param-btn" plain
+                  @click="handleOtherBtnClick('questionTypeSelection', 'fillBlanks', '填空题')">填空题</el-button></el-col>
+              <el-col :span="8"><el-button class="param-btn" plain
+                  @click="handleOtherBtnClick('questionTypeSelection', 'scaleQuestions', '量表题')">量表题</el-button></el-col>
+              <el-col :span="8"><el-button class="param-btn" plain
+                  @click="handleOtherBtnClick('questionTypeSelection', 'matrixScale', '矩阵量表')">矩阵量表</el-button></el-col>
+            </el-row>
+          </div>
+        </div>
+      </el-aside>
+      <el-container>
+        <el-main style="padding-left: 20px;">
+          <el-scrollbar>
+            <div style="width: 50%;">
+              <el-form label-width="100px" label-position="left" :model="form" ref="screeningAssessmentConfigFormRef">
+                <el-form-item label="量表类型:" required>
+                  <el-radio-group v-model="form.type">
+                    <el-radio :label="item.label" :value="item.value" v-for="item in scale_type" :key="item.value"/>                     
+                  </el-radio-group>
+                </el-form-item>
+                <el-form-item label="适用条件:" required>
+                  <!-- 可留空或加说明 -->
+                </el-form-item>
+                <el-form-item label="性别:" style="margin-left: 20px;">
+                  <el-radio-group v-model="form.gender" style="margin-left: -50px;">
+                    <el-radio label="不限" value="-1">不限</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+                <el-form-item label="年龄:" style="margin-left: 20px;">
+                  <el-radio-group v-model="form.age" style="margin-left: -50px;">
+                    <el-radio label="不限" style="width: 50%" value="-1">不限</el-radio>
+                  </el-radio-group>
+                </el-form-item>
+                <el-form-item label="量表名称:" required>
+                  <el-input v-model="form.name" maxlength="50" show-word-limit placeholder="请输入" />
+                </el-form-item>
+                <el-form-item label="量表说明:">
+                  <el-input v-model="form.description" maxlength="50" show-word-limit placeholder="请输入" />
+                </el-form-item>
+                <el-form-item label="备注:">
+                  <el-input type="textarea" v-model="form.remark" maxlength="500" show-word-limit placeholder="请输入"
+                    :rows="3" />
+                </el-form-item>
+              </el-form>
+            </div>
+
+            <div v-if="baseInfo.questionType" @click="selectBaseInfo" :class="{ selectedDiv: baseInfo.selected }">
+              <span>&nbsp;</span>
+              <div style="width: 50%;margin-top: 32px;">
+                <el-form label-width="100px" label-position="left">
+                  <el-form-item label="1. 标题:" required>
+                    <el-input :value="baseInfo.title" readonly size="large" />
+                  </el-form-item>
+                  <div style="margin-left: 60px;">
+                    <template v-for="ctl in baseInfo.contentList">
+                      <el-form-item label="填空:">
+                        <el-input v-model="ctl.label" readonly style="margin-left: -50px;" size="large">
+                          <template #append><el-button @click="handleBaseBtnDelete(ctl.nameEn)">-</el-button></template>
+                        </el-input>
+                      </el-form-item>
+                    </template>
+                  </div>
+                </el-form>
+              </div>
+              <div style="display: flex; align-items: right;justify-content: right;padding-right: 50px;"
+                v-show="baseInfo.selected">
+                <el-button type="primary" icon="Delete" plain @click="deleteBaseInfo">删除</el-button>
+              </div>
+              <span>&nbsp;</span>
+            </div>
+
+            <template v-for="other, index in otherInfo">
+              <div style="overflow-x: scroll;" v-if="other.questionChildType == 'scaleQuestions'">
+                <div class="custom-radio-grid">
+                  <div v-for="n in other.contentList.length" :key="n"
+                    :class="['custom-radio-cell', { 'is-checked': other.contentList.length === n, 'is-last': n > 1 }]">
+                    {{ n }}
+                  </div>
+                </div>
+
+                <div class="custom-radio-grid">
+                  <div v-for="ctl, n in other.contentList" :key="ctl.nameEn" style="border: none;"
+                    :class="['custom-radio-cell']">
+                    {{ ctl.label }}
+                  </div>
+                </div>
+              </div>
+
+              <div style="border-bottom: 1px solid #e5e7eb;width: 98%;" v-if="other.questionChildType == 'matrixScale'">
+
+                <el-row :gutter="10" class="mb8">
+                  <el-col :span="3" style="margin-top: 30px;">
+                  </el-col>
+                  <template v-for="clt in other.contentList">
+                    <template v-if="clt.optionFlag">
+                      <el-col :span="1" align="center" style="margin-top: 30px;margin-left: 30px;">
+                        {{ clt.optionContent }}
+                      </el-col>
+                    </template>
+                  </template>
+                </el-row>
+
+                <template v-for="clt, idx in other.contentList">
+                  <el-row :gutter="10" class="mb8">
+                    <el-col :span="3" style="margin-top: 10px;">
+                      <template v-if="clt.labelFlag">
+                        标题{{ idx + 1 }}:{{ clt.label }}
+                      </template>
+                    </el-col>
+                    <template v-for="clt2, idx in other.contentList">
+                      <el-col :span="1" align="center" style="margin-left: 30px;">
+                        <template v-if="clt2.optionFlag && clt.labelFlag">
+                          <el-radio-group>
+                            <el-radio value="1" size="large" />
+                          </el-radio-group>
+                        </template>
+                      </el-col>
+                    </template>
+                  </el-row>
+                </template>
+              </div>
+
+              <div style="border-bottom: 1px solid #e5e7eb;width: 98%;" :class="{ selectedDiv: other.selected }"
+                @click="selectOtherInfo(index)">
+                <span>&nbsp;</span>
+                <template v-if="other.questionType != 'questionTypeSelection'">
+                  <el-row :gutter="10" class="mb8">
+                    <el-col :span="12">
+                      <el-form-item :label="(index + 1 + baseInfo.contentList.length) + '. 标题:'" required>
+                        <el-input readonly style="width: 100%;" v-model="other.title" size="large" />
+                      </el-form-item>
+                    </el-col>
+                    <el-col :span="2" align="center">
+                    </el-col>
+                    <el-col :span="3">
+                    </el-col>
+                    <el-col :span="3">
+                    </el-col>
+                    <el-col :span="4">
+                    </el-col>
+                  </el-row>
+                </template>
+                <template v-if="other.questionType == 'questionTypeSelection'">
+                  <el-row :gutter="10" class="mb8">
+                    <el-col :span="12">
+                      <el-form-item :label="(index + 1 + baseInfo.contentList.length) + '. 标题:'" required>
+                        <el-input style="width: 100%;" v-model="other.title" placeholder="请输入标题" size="large"
+                          show-word-limit maxlength="150" />
+                      </el-form-item>
+                    </el-col>
+                    <el-col :span="2" align="center">
+                      <el-checkbox label="是否必填" v-model="other.required" />
+                    </el-col>
+                    <el-col :span="3">
+                      <el-select v-model="other.arrangement" placeholder="请选择" clearable
+                        v-if="other.questionChildType == 'multipleChoice' || other.questionChildType == 'singleChoice' || other.questionChildType == 'fillBlanks'">
+                        <el-option v-for="dict in arrangement_method" :key="dict.value" :label="dict.label"
+                          :value="dict.value"></el-option>
+                      </el-select>
+                    </el-col>
+                    <el-col :span="5">
+                      <div v-if="other.questionChildType == 'multipleChoice'">
+                        <el-form-item label="计分方式" id="jffs">
+                          <el-select v-model="other.scoreMethod" placeholder="请选择" clearable>
+                            <el-option v-for="dict in score_method" :key="dict.value" :label="dict.label"
+                              :value="dict.value"></el-option>
+                          </el-select>
+                        </el-form-item>
+
+                      </div>
+                    </el-col>
+                    <el-col :span="2">
+                    </el-col>
+                  </el-row>
+                </template>
+
+                <el-row :gutter="10" class="mb8" v-if="other.questionChildType == 'singleChoice'"
+                  style="background-color: #e5e7eb;height: 40px;line-height: 40px;width: 98%;">
+                  <el-col :span="12">
+                    <div style="text-align: center;">选项(单选)</div>
+                  </el-col>
+                  <el-col :span="2">
+                    <div style="text-align: center;">添加图片</div>
+                  </el-col>
+                  <el-col :span="3">
+                    <div style="text-align: center;">分值设置</div>
+                  </el-col>
+                  <el-col :span="3">
+                    <div style="text-align: left;">允许填空</div>
+                  </el-col>
+                  <el-col :span="4">
+                  </el-col>
+                </el-row>
+                <el-row :gutter="10" class="mb8" v-if="other.questionChildType == 'multipleChoice'"
+                  style="background-color: #e5e7eb;height: 40px;line-height: 40px;width: 98%;">
+                  <el-col :span="12">
+                    <div style="text-align: center;"> 选项(多选)</div>
+                  </el-col>
+                  <el-col :span="2">
+                    <div style="text-align: center;">添加图片</div>
+                  </el-col>
+                  <el-col :span="3">
+                    <div style="text-align: center;">分值设置</div>
+                  </el-col>
+                  <el-col :span="3">
+                    <div style="text-align: left;">允许填空</div>
+                  </el-col>
+                  <el-col :span="4">
+                  </el-col>
+                </el-row>
+                <el-row :gutter="10" class="mb8" v-if="other.questionChildType == 'matrixScale'"
+                  style="background-color: #e5e7eb;height: 40px;line-height: 40px;width: 98%;">
+                  <el-col :span="10">
+                    <div style="text-align: center;"> 选项题目(矩阵量表)</div>
+                  </el-col>
+                  <el-col :span="4">
+                    <div style="text-align: center;">选项内容</div>
+                  </el-col>
+                  <el-col :span="3">
+                    <div style="text-align: center;">分值设置</div>
+                  </el-col>
+                  <el-col :span="3">
+                    <div style="text-align: left;">允许填空</div>
+                  </el-col>
+                  <el-col :span="4">
+                  </el-col>
+                </el-row>
+                <el-row :gutter="10" class="mb8"
+                  v-if="other.questionChildType == 'anthropometry' && other.lastType == '2'"
+                  style="background-color: #e5e7eb;height: 40px;line-height: 40px;width: 98%;">
+                  <el-col :span="12">
+                    <div style="text-align: center;">选项(单选)</div>
+                  </el-col>
+                  <el-col :span="2">
+                  </el-col>
+                  <el-col :span="3">
+                  </el-col>
+                  <el-col :span="3">
+                  </el-col>
+                  <el-col :span="4">
+                  </el-col>
+                </el-row>
+                <el-row :gutter="10" class="mb8" v-if="(other.questionChildType == 'anthropometry' && other.lastType == '4')
+                  || (other.questionChildType == 'dietaryStatus' && other.lastType == '2')"
+                  style="background-color: #e5e7eb;height: 40px;line-height: 40px;width: 98%;">
+                  <el-col :span="12">
+                    <div style="text-align: center;">选项(单选)</div>
+                  </el-col>
+                  <el-col :span="2">
+                    <div style="text-align: center;">分值设置</div>
+                  </el-col>
+                  <el-col :span="3">
+                  </el-col>
+                  <el-col :span="3">
+                  </el-col>
+                  <el-col :span="4">
+                  </el-col>
+                </el-row>
+                <el-row :gutter="10" class="mb8" v-if="other.questionChildType == 'scaleQuestions'"
+                  style="background-color: #e5e7eb;height: 40px;line-height: 40px;width: 98%;">
+                  <el-col :span="12">
+                    <div style="text-align: center;">选项(量表)</div>
+                  </el-col>
+                  <el-col :span="2">
+                    <div style="text-align: center;">分值设置</div>
+                  </el-col>
+                  <el-col :span="3">
+                  </el-col>
+                  <el-col :span="3">
+                  </el-col>
+                  <el-col :span="4">
+                  </el-col>
+                </el-row>
+                <el-row :gutter="10" class="mb8" v-if="other.questionChildType == 'fillBlanks'"
+                  style="background-color: #e5e7eb;height: 40px;line-height: 40px;width: 98%;">
+                  <el-col :span="12">
+                    <div style="text-align: center;">选项(填空)</div>
+                  </el-col>
+                  <el-col :span="2">
+                    <div style="text-align: center;">添加图片</div>
+                  </el-col>
+                  <el-col :span="3">
+                    <div style="text-align: center;">分值设置</div>
+                  </el-col>
+                  <el-col :span="3">
+                    <div style="text-align: left;">单位</div>
+                  </el-col>
+                  <el-col :span="4">
+                  </el-col>
+                </el-row>
+
+
+                <template v-if="other.questionChildType == 'singleChoice' || other.questionChildType == 'fillBlanks'
+                  || other.questionChildType == 'multipleChoice'">
+                  <template v-for="ctl, idx in other.contentList">
+                    <el-row :gutter="10" class="mb8" style="margin-top: 20px;width: 98%;">
+                      <el-col :span="12">
+                        <el-form-item required style="margin-left: 40px;">
+                          <el-radio-group>
+                            <el-radio :disabled="true">
+                              选项{{ idx + 1 }}:
+                              <el-input size="large" style="width: 430px;" v-model="ctl.label" show-word-limit
+                                maxlength="150">
+                                <template #prepend><el-button
+                                    @click="handleOtherBtnAdd(index, other.questionChildType)">+</el-button></template>
+                                <template #append><el-button
+                                    @click="handleOtherBtnDelete(index, idx)">-</el-button></template>
+                              </el-input>
+                            </el-radio>
+                          </el-radio-group>
+                        </el-form-item>
+                      </el-col>
+                      <el-col :span="2" align="center" style="margin-top: -5px;">
+                        <miniImageUpload :limit="1" v-model="ctl.img" />
+                      </el-col>
+                      <el-col :span="3" align="center" style="margin-top: -5px;">
+                        <el-input style="width: 60px;" size="large" v-model="ctl.score" />
+                      </el-col>
+                      <el-col :span="3" align="left" style="margin-top: -5px;">
+                        <template v-if="other.questionChildType == 'fillBlanks'">
+                          <el-input style="width: 40px;" size="large" v-model="ctl.unit" />
+                        </template>
+                        <template v-if="other.questionChildType != 'fillBlanks'">
+                          <el-checkbox v-model="ctl.allowFillBlank" />
+                        </template>
+                      </el-col>
+                      <el-col :span="4" align="center">
+                      </el-col>
+                    </el-row>
+                  </template>
+                </template>
+
+
+                <template v-if="other.questionChildType == 'matrixScale'">
+                  <template v-for="ctl, idx in other.contentList">
+                    <el-row :gutter="10" class="mb8" style="margin-top: 20px;width: 98%;">
+                      <el-col :span="10" style="margin-top: -5px;">
+                        <template v-if="ctl.labelFlag">
+                          <el-form-item required style="margin-left: 40px;">
+                            行标题{{ idx + 1 }}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+                            <el-input size="large" style="width: 330px;" v-model="ctl.label" show-word-limit
+                              maxlength="150">
+                              <template #prepend><el-button
+                                  @click="handleMatrixScaleAdd(index, idx, true)">+</el-button></template>
+                              <template #append><el-button
+                                  @click="handleMatrixScaleDelete(index, idx, true)">-</el-button></template>
+                            </el-input>
+                          </el-form-item>
+                        </template>
+                      </el-col>
+                      <el-col :span="4" align="center" style="margin-top: -5px;">
+                        <template v-if="ctl.optionFlag">
+                          <el-input size="large" v-model="ctl.optionContent" show-word-limit maxlength="150">
+                            <template #prepend><el-button
+                                @click="handleMatrixScaleAdd(index, idx, false)">+</el-button></template>
+                            <template #append><el-button
+                                @click="handleMatrixScaleDelete(index, idx, false)">-</el-button></template>
+                          </el-input>
+                        </template>
+                      </el-col>
+                      <el-col :span="3" align="center" style="margin-top: -5px;">
+                        <miniImageUpload :limit="1" v-model="ctl.img" />
+                      </el-col>
+                      <el-col :span="3" align="left" style="margin-top: -5px;">
+                        <el-input style="width: 60px;" size="large" v-model="ctl.score" />
+                      </el-col>
+                      <el-col :span="4" align="center">
+                      </el-col>
+                    </el-row>
+                  </template>
+                </template>
+
+                <template v-if="(other.questionChildType == 'anthropometry' && (other.lastType == '1' || other.lastType == '3')
+                  || (other.questionChildType == 'dietaryStatus' && other.lastType != '2'))">
+                  <template v-for="ctl, idx in other.contentList">
+                    <el-row :gutter="10" class="mb8" :style="{ marginTop: idx == 0 ? '20px' : '0px' }"
+                      style="width: 98%;">
+                      <el-col :span="12">
+                        <div style="margin-left: 60px;margin-top: -15px;">
+                          <el-form-item label="填空:">
+                            <el-input readonly size="large" v-model="ctl.label">
+                              <template #append><el-button
+                                  @click="handleOtherBtnDelete(index, idx)">-</el-button></template>
+                            </el-input>
+                          </el-form-item>
+                        </div>
+                      </el-col>
+                      <el-col :span="2" align="center" style="margin-top: -5px;">
+                      </el-col>
+                      <el-col :span="3" align="center">
+                      </el-col>
+                      <el-col :span="3" align="center">
+                      </el-col>
+                      <el-col :span="4" align="center">
+                      </el-col>
+                    </el-row>
+                  </template>
+                </template>
+
+                <template v-if="((other.questionChildType == 'anthropometry' && other.lastType == '4')
+                  || (other.questionChildType == 'dietaryStatus' && other.lastType == '2'))">
+                  <template v-for="ctl, idx in other.contentList">
+                    <el-row :gutter="10" class="mb8" :style="{ marginTop: idx == 0 ? '20px' : '0px' }"
+                      style="width: 98%;">
+                      <el-col :span="12">
+                        <el-form-item required style="margin-left: 40px;"
+                          :style="{ marginTop: idx == 0 ? '0px' : '-5px' }">
+                          <el-radio-group>
+                            <el-radio :disabled="true">
+                              选项{{ idx + 1 }}:
+                              <el-input readonly size="large" style="width: 430px;" v-model="ctl.label">
+                                <template #append><el-button
+                                    @click="handleOtherBtnDelete(index, idx)">-</el-button></template>
+                              </el-input>
+                            </el-radio>
+                          </el-radio-group>
+                        </el-form-item>
+                      </el-col>
+                      <el-col :span="2" align="center" style="margin-top: -5px;">
+                        <el-input style="width: 60px;" v-model="ctl.score" size="large" />
+                      </el-col>
+                      <el-col :span="3" align="center">
+                      </el-col>
+                      <el-col :span="3" align="center">
+                      </el-col>
+                      <el-col :span="4" align="center">
+                      </el-col>
+                    </el-row>
+                  </template>
+                </template>
+
+                <template v-if="other.questionChildType == 'scaleQuestions'">
+                  <template v-for="ctl, idx in other.contentList">
+                    <el-row :gutter="10" class="mb8" :style="{ marginTop: idx == 0 ? '20px' : '0px' }"
+                      style="width: 98%;">
+                      <el-col :span="12">
+                        <el-form-item required style="margin-left: 40px;"
+                          :style="{ marginTop: idx == 0 ? '0px' : '-5px' }">
+                          {{ idx + 1 }}:
+                          <el-input size="large" style="width: 430px;" v-model="ctl.label" show-word-limit
+                            maxlength="150">
+                            <template #prepend><el-button
+                                @click="handleOtherBtnAdd(index, other.questionChildType)">+</el-button></template>
+                            <template #append><el-button
+                                @click="handleOtherBtnDelete(index, idx)">-</el-button></template>
+                          </el-input>
+                        </el-form-item>
+                      </el-col>
+                      <el-col :span="2" align="center" style="margin-top: -5px;">
+                        <el-input style="width: 60px;" v-model="ctl.score" size="large" />
+                      </el-col>
+                      <el-col :span="3" align="center">
+                      </el-col>
+                      <el-col :span="3" align="center">
+                      </el-col>
+                      <el-col :span="4" align="center">
+                      </el-col>
+                    </el-row>
+                  </template>
+                </template>
+
+                <template v-if="(other.questionChildType == 'anthropometry' && other.lastType == '2'
+                  || (other.questionChildType == 'dietaryStatus' && other.lastType == '12'))">
+                  <template v-for="ctl, idx in other.contentList">
+                    <el-row :gutter="10" class="mb8" :style="{ marginTop: idx == 0 ? '20px' : '0px' }"
+                      style="width: 98%;">
+                      <el-col :span="12">
+                        <el-form-item required style="margin-left: 40px;"
+                          :style="{ marginTop: idx == 0 ? '0px' : '-5px' }">
+                          <el-radio-group>
+                            <el-radio :disabled="true">
+                              选项{{ idx + 1 }}:
+                              <el-input readonly size="large" style="width: 430px;" v-model="ctl.label">
+                                <template #append><el-button
+                                    @click="handleOtherBtnDelete(index, idx)">-</el-button></template>
+                              </el-input>
+                            </el-radio>
+                          </el-radio-group>
+                        </el-form-item>
+                      </el-col>
+                      <el-col :span="2" align="center" style="margin-top: -5px;">
+                      </el-col>
+                      <el-col :span="3" align="center">
+                      </el-col>
+                      <el-col :span="3" align="center">
+                      </el-col>
+                      <el-col :span="4" align="center">
+                      </el-col>
+                    </el-row>
+                  </template>
+                </template>
+
+                <div style="display: flex; align-items: right;justify-content: right;padding-right: 50px;"
+                  v-show="other.selected">
+                  <el-button type="primary" icon="Top" plain :disabled="index == 0"
+                    @click="moveUp(index)">上移</el-button>
+                  <el-button type="primary" icon="Bottom" plain :disabled="index + 1 == otherInfo.length"
+                    @click="moveDown(index)">下移</el-button>
+                  <el-button type="primary" icon="Upload" plain :disabled="index == 0"
+                    @click="topUp(index)">置顶</el-button>
+                  <el-button type="primary" icon="Download" plain :disabled="index + 1 == otherInfo.length"
+                    @click="bottomUp(index)">置底</el-button>
+                  <el-button type="primary" icon="Delete" plain @click="deleteOtherInfo(index)">删除</el-button>
+                </div>
+                <span>&nbsp;</span>
+              </div>
+            </template>
+
+
+          </el-scrollbar>
+        </el-main>
+      </el-container>
+    </el-container>
+  </div>
+</template>
+
+<script setup name="ScreeningAssessmentConfig" lang="ts">
+import { ref, onMounted, onUnmounted } from 'vue';
+import { listScreeningAssessmentConfig, getScreeningAssessmentConfig, delScreeningAssessmentConfig, addScreeningAssessmentConfig, updateScreeningAssessmentConfig } from '@/api/system/screeningAssessmentConfig';
+import { ScreeningAssessmentConfigVO, ScreeningAssessmentConfigQuery, ScreeningAssessmentConfigForm, QuestionAttrVO, QuestionVO } from '@/api/system/screeningAssessmentConfig/types';
+
+const route = useRoute();
+const showSystemParam = ref(true);
+const showQuestionType = ref(true);
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { arrangement_method, score_method ,scale_type} = toRefs<any>(proxy?.useDict('scale_type','arrangement_method', 'score_method'));
+
+const screeningAssessmentConfigList = ref<ScreeningAssessmentConfigVO[]>([]);
+const buttonLoading = ref(false);
+const loading = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const baseInfo = ref<QuestionVO>({
+  questionId: '',
+  title: '',
+  questionType: '',
+  content: '',
+  selected: false,
+  contentList: [],
+});
+const otherInfo = ref<QuestionVO[]>([]);
+
+const queryFormRef = ref<ElFormInstance>();
+const screeningAssessmentConfigFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: ScreeningAssessmentConfigForm = {
+  configId: undefined,
+  type: '1',
+  gender: '-1',
+  age: '-1',
+  name: undefined,
+  description: undefined,
+  remark: undefined,
+  status: undefined,
+  baseInfo: undefined,
+  otherInfo: undefined
+}
+const data = reactive<PageData<ScreeningAssessmentConfigForm, ScreeningAssessmentConfigQuery>>({
+  form: { ...initFormData },
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    type: undefined,
+    gender: undefined,
+    age: undefined,
+    name: undefined,
+    description: undefined,
+    status: undefined,
+    params: {
+    }
+  },
+  rules: {
+    age: [
+      { required: true, message: "年龄不能为空", trigger: "blur" }
+    ],
+    gender: [
+      { required: true, message: "性别不能为空", trigger: "blur" }
+    ],
+    type: [
+      { required: true, message: "量表类型不能为空", trigger: "change" }
+    ],
+    name: [
+      { required: true, message: "量表名称不能为空", trigger: "blur" }
+    ],
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询筛查/评估配置列表 */
+const getList = async () => {
+  const eId = route.params && route.params.id;
+  const vId = route.params && route.params.vid;
+  let id=undefined;
+  if(vId){    
+    id=vId;
+  }else{    
+    id=eId;
+  }
+
+  if (id&&id!='0') {
+    loading.value = true;
+    const res = await getScreeningAssessmentConfig(id as string);
+    Object.assign(form.value, res.data);
+    if(res.data.baseInfo){
+      baseInfo.value = res.data.baseInfo;
+    }
+    if(res.data.otherInfo){
+      otherInfo.value = res.data.otherInfo;
+    }
+    loading.value = false;
+  }
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = { ...initFormData };
+  screeningAssessmentConfigFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ScreeningAssessmentConfigVO[]) => {
+  ids.value = selection.map(item => item.configId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = "添加筛查/评估配置";
+}
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: ScreeningAssessmentConfigVO) => {
+  reset();
+  const _configId = row?.configId || ids.value[0]
+  const res = await getScreeningAssessmentConfig(_configId);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = "修改筛查/评估配置";
+}
+
+/** 提交按钮 */
+const submitForm = () => {
+  screeningAssessmentConfigFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      if (!baseInfo.value.questionType && otherInfo.value.length == 0) {
+        proxy?.$modal.msgError("请选择题型");
+        return;
+      }
+
+      let baseNum=!baseInfo.value.questionType?0:1;
+      let lineNum=0;
+      for (let item of otherInfo.value) {
+        lineNum++;
+        if (!item.title || item.title.trim().length < 1) {       
+          proxy?.$modal.msgError("第"+(baseNum+lineNum)+"行标题为空");
+          return;
+        }
+        for (let ch of item.contentList) {
+          if (!ch.label || ch.label.trim().length < 1) {
+            proxy?.$modal.msgError("第"+(baseNum+lineNum)+"行标题的选项有空值");
+            return;
+          }
+          if (item.questionChildType=='matrixScale'&&(!ch.optionContent || ch.optionContent.trim().length < 1)) {
+            proxy?.$modal.msgError("第"+(baseNum+lineNum)+"行标题的选项内容为空");
+            return;
+          }
+        }
+      }
+
+      if(baseInfo.value.questionType&&baseInfo.value.questionType.length>0){
+        form.value.baseInfo = baseInfo.value;
+      }
+      if(otherInfo.value&&otherInfo.value.length>0){
+        form.value.otherInfo = otherInfo.value;
+      }
+      buttonLoading.value = true;
+      if (form.value.configId) {
+        await updateScreeningAssessmentConfig(form.value).finally(() => buttonLoading.value = false);
+      } else {
+        await addScreeningAssessmentConfig(form.value).finally(() => buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功");
+      close();
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: ScreeningAssessmentConfigVO) => {
+  const _configIds = row?.configId || ids.value;
+  await proxy?.$modal.confirm('是否确认删除筛查/评估配置编号为"' + _configIds + '"的数据项?').finally(() => loading.value = false);
+  await delScreeningAssessmentConfig(_configIds);
+  proxy?.$modal.msgSuccess("删除成功");
+  await getList();
+}
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download('system/screeningAssessmentConfig/export', {
+    ...queryParams.value
+  }, `screeningAssessmentConfig_${new Date().getTime()}.xlsx`)
+}
+
+/** 预览按钮操作 */
+const handlePreview = () => {
+  // Implementation of handlePreview function
+}
+
+/** 关闭按钮操作 */
+const close = () => {
+  const obj: RouteLocationNormalized = {
+    fullPath: '',
+    hash: '',
+    matched: [],
+    meta: undefined,
+    name: undefined,
+    params: undefined,
+    query: undefined,
+    redirectedFrom: undefined,
+    path: '/argManage/screeningAssessmentConfig'
+  };
+  proxy?.$tab.closeOpenPage(obj);
+}
+
+function handleOtherBtnClick(questionType: string, nameEn: string, nameCn: string) {
+  if (otherInfo.value.length > 0 && questionType == 'otherInfo') {
+    let newList = []
+    for (let i = 0, len = otherInfo.value.length; i < len; i++) {
+      let other = otherInfo.value[i];
+      if (other.questionType == 'otherInfo' && other.questionChildType == nameEn) {
+        continue;
+      }
+      newList.push(other);
+    }
+
+    if (newList.length != otherInfo.value.length) {
+      otherInfo.value = newList;
+    }
+  }
+
+  let question: QuestionVO = {
+    questionId: '',
+    title: nameCn,
+    questionType: questionType,
+    questionChildType: nameEn,
+    lastType: '1',
+    selected: false,
+    required: false,
+    scoreMethod: '1',
+    arrangement: '1',
+    content: '',
+    contentList: [],
+  };
+  otherInfo.value.push(question);
+
+  if ('anthropometry' == nameEn) {
+    question.contentList.push({
+      label: '身高',
+      value: undefined,
+      nameEn: 'height',
+    });
+    question.contentList.push({
+      label: '当前体重',
+      value: undefined,
+      nameEn: 'currentWeight',
+    });
+    question.contentList.push({
+      label: '出生体重',
+      value: undefined,
+      nameEn: 'birthWeight',
+    });
+    question.contentList.push({
+      label: '理想体重',
+      value: undefined,
+      nameEn: 'idealWeight',
+    });
+    question.contentList.push({
+      label: 'BMI',
+      value: undefined,
+      nameEn: 'BMI',
+    });
+    question.contentList.push({
+      label: '握力',
+      value: undefined,
+      nameEn: 'gripStrength',
+    });
+    question.contentList.push({
+      label: '皮褶厚度',
+      value: undefined,
+      nameEn: 'leatherFoldThickness',
+    });
+    question.contentList.push({
+      label: '腰围',
+      value: undefined,
+      nameEn: 'waistCircumference',
+    });
+    question.contentList.push({
+      label: '上臂围',
+      value: undefined,
+      nameEn: 'upperArmCircumference',
+    });
+    question.contentList.push({
+      label: '小腿围',
+      value: undefined,
+      nameEn: 'calfCircumference',
+    });
+    question.contentList.push({
+      label: '其他测量指标',
+      value: undefined,
+      nameEn: 'otherMeasurementIndicators',
+    });
+
+
+    question = {
+      questionId: '',
+      title: '请选择调查时间',
+      questionType: questionType,
+      questionChildType: nameEn,
+      content: '',
+      lastType: '2',
+      contentList: [],
+    };
+    otherInfo.value.push(question);
+    question.contentList.push({
+      label: '三个月前',
+      formType: 'radio',
+      value: undefined,
+      nameEn: 'threeMonthsAgo',
+    });
+    question.contentList.push({
+      label: '两个月前',
+      value: undefined,
+      formType: 'radio',
+      nameEn: 'twoMonthsAgo',
+    });
+    question.contentList.push({
+      label: '一个月前',
+      formType: 'radio',
+      value: undefined,
+      nameEn: 'oneMonthsAgo',
+    });
+
+
+    question = {
+      questionId: '',
+      title: '请填入已选调查时间体重',
+      questionType: questionType,
+      questionChildType: nameEn,
+      lastType: '3',
+      content: '',
+      contentList: [],
+    };
+    otherInfo.value.push(question);
+    question.contentList.push({
+      label: '过去体重',
+      value: undefined,
+      nameEn: 'pastWeight',
+    });
+
+
+    question = {
+      questionId: '',
+      title: '1-3个月内体重变化情况',
+      questionType: questionType,
+      questionChildType: nameEn,
+      lastType: '4',
+      content: '',
+      contentList: [],
+    };
+    otherInfo.value.push(question);
+    question.contentList.push({
+      label: '近3个月体重无显著变化',
+      formType: 'radioScore',
+      value: undefined,
+      nameEn: 'noChangeWeight',
+    });
+    question.contentList.push({
+      label: '近3个月体重丢失>5%',
+      formType: 'radioScore',
+      value: undefined,
+      nameEn: 'weightLoss5In3Months',
+    });
+    question.contentList.push({
+      label: '近2个月体重丢失>5%',
+      formType: 'radioScore',
+      value: undefined,
+      nameEn: 'weightLoss5In2Months',
+    });
+    question.contentList.push({
+      label: '近1个月体重丢失>5%',
+      formType: 'radioScore',
+      value: undefined,
+      nameEn: 'weightLoss5In1Months',
+    });
+
+    for (let i = 0, len = question.contentList.length; i < len; i++) {
+      question.contentList[i].score = i.toString();
+    }
+    return;
+  }
+
+  if ('dietaryStatus' == nameEn) {
+    question.contentList.push({
+      label: '平素膳食餐次',
+      value: undefined,
+      nameEn: 'regularMealSchedule',
+    });
+
+
+    question = {
+      questionId: '',
+      title: '近1周食物摄入量是否减少',
+      questionType: questionType,
+      questionChildType: nameEn,
+      lastType: '2',
+      content: '',
+      contentList: [],
+    };
+    otherInfo.value.push(question);
+    question.contentList.push({
+      label: '无变化',
+      formType: 'radioScore',
+      value: undefined,
+      nameEn: 'noChange',
+    });
+    question.contentList.push({
+      label: '较从前减少25%~50%',
+      formType: 'radioScore',
+      value: undefined,
+      nameEn: 'decreasedBy25To50',
+    });
+    question.contentList.push({
+      label: '较从前减少51%~75%',
+      formType: 'radioScore',
+      value: undefined,
+      nameEn: 'decreasedBy51To75',
+    });
+    question.contentList.push({
+      label: '较从前减少76%~100%',
+      formType: 'radioScore',
+      value: undefined,
+      nameEn: 'decreasedBy76To100',
+    });
+    for (let i = 0, len = question.contentList.length; i < len; i++) {
+      question.contentList[i].score = i.toString();
+    }
+
+
+    question = {
+      questionId: '',
+      title: '近1周食物摄入种类(/日)',
+      questionType: questionType,
+      questionChildType: nameEn,
+      lastType: '3',
+      content: '',
+      contentList: [],
+    };
+    otherInfo.value.push(question);
+    question.contentList.push({
+      label: '奶及奶制品',
+      value: undefined,
+      nameEn: 'milkAndDairyProducts',
+    });
+    question.contentList.push({
+      label: '坚果类',
+      value: undefined,
+      nameEn: 'nutCategory',
+    });
+    question.contentList.push({
+      label: '大豆类',
+      value: undefined,
+      nameEn: 'largeBeans',
+    });
+    question.contentList.push({
+      label: '肉蛋水产类',
+      value: undefined,
+      nameEn: 'meatEggAndAquaticProducts',
+    });
+    question.contentList.push({
+      label: '蔬菜类',
+      value: undefined,
+      nameEn: 'vegetables',
+    });
+    question.contentList.push({
+      label: '水果类',
+      value: undefined,
+      nameEn: 'fruitCategory',
+    });
+    question.contentList.push({
+      label: '谷薯杂豆类',
+      value: undefined,
+      nameEn: 'grainPotatoAndMixedBeans',
+    });
+    question.contentList.push({
+      label: '调料类',
+      value: undefined,
+      nameEn: 'seasoning',
+    });
+    question.contentList.push({
+      label: '水',
+      value: undefined,
+      nameEn: 'water',
+    });
+
+
+    question = {
+      questionId: '',
+      title: '近1周能量-营养素摄入分析(/日)',
+      questionType: questionType,
+      questionChildType: nameEn,
+      lastType: '4',
+      content: '',
+      contentList: [],
+    };
+    otherInfo.value.push(question);
+    question.contentList.push({
+      label: '总能量',
+      value: undefined,
+      nameEn: 'totalEnergy',
+    });
+    question.contentList.push({
+      label: '总氮量',
+      value: undefined,
+      nameEn: 'totalNitrogenContent',
+    });
+    question.contentList.push({
+      label: '氮/能量比值:1',
+      value: undefined,
+      nameEn: 'nitrogenEnergyRatio1',
+    });
+    question.contentList.push({
+      label: '蛋白质含量',
+      value: undefined,
+      nameEn: 'proteinContent',
+    });
+    question.contentList.push({
+      label: '脂肪含量',
+      value: undefined,
+      nameEn: 'fatContent',
+    });
+    question.contentList.push({
+      label: '碳水化合物含量',
+      value: undefined,
+      nameEn: 'carbohydrateContent',
+    });
+    question.contentList.push({
+      label: '蛋白质热比',
+      value: undefined,
+      nameEn: 'proteinHeatRatio',
+    });
+    question.contentList.push({
+      label: '脂肪热比',
+      value: undefined,
+      nameEn: 'protefatToHeatRatioinHeatRatio',
+    });
+    question.contentList.push({
+      label: '碳水化合物热比',
+      value: undefined,
+      nameEn: 'carbohydrateHeatRatio',
+    });
+    question.contentList.push({
+      label: '盐',
+      value: undefined,
+      nameEn: 'salt',
+    });
+    question.contentList.push({
+      label: '钠',
+      value: undefined,
+      nameEn: 'sodium',
+    });
+    return;
+  }
+
+  if ('singleChoice' == nameEn) {
+    question.title = undefined;
+    question.required = false;
+    question.arrangement = '1';
+    question.increment = 0;
+
+    question.contentList.push({
+      label: undefined,
+      value: undefined,
+      nameEn: 'single' + question.increment.toString(),
+      allowFillBlank: false,
+      img: undefined,
+      score: '0.0',
+    });
+    return;
+  }
+
+  if ('multipleChoice' == nameEn) {
+    question.title = undefined;
+    question.required = false;
+    question.arrangement = '1';
+    question.scoreMethod = '1';
+    question.increment = 0;
+
+    question.contentList.push({
+      label: undefined,
+      value: undefined,
+      nameEn: 'multiple' + question.increment.toString(),
+      allowFillBlank: false,
+      img: undefined,
+      score: '0.0',
+    });
+    return;
+  }
+
+  if ('fillBlanks' == nameEn) {
+    question.title = undefined;
+    question.required = false;
+    question.arrangement = '1';
+    question.increment = 0;
+
+    question.contentList.push({
+      label: undefined,
+      value: undefined,
+      nameEn: 'fill' + question.increment.toString(),
+      img: undefined,
+      score: '0.0',
+      unit: '',
+    });
+    return;
+  }
+
+  if ('scaleQuestions' == nameEn) {
+    question.title = undefined;
+    question.required = false;
+    question.increment = 0;
+
+    question.contentList.push({
+      label: undefined,
+      value: undefined,
+      nameEn: 'scale' + question.increment.toString(),
+      score: '0.0',
+    });
+
+    return;
+  }
+
+  if ('matrixScale' == nameEn) {
+    question.title = undefined;
+    question.required = false;
+    question.increment = 0;
+
+    question.contentList.push({
+      labelFlag: true,
+      label: undefined,
+      value: undefined,
+      nameEn: 'matrix' + question.increment.toString(),
+      score: '0.0',
+      optionContent: '',
+      optionFlag: true,
+    });
+
+    return;
+  }
+}
+
+
+function handleBaseBtnClick(nameEn: string, nameCn: string) {
+  if (baseInfo.value.contentList.length < 1) {
+    baseInfo.value.title = '基本信息';
+    baseInfo.value.questionType = 'baseInfo';
+    baseInfo.value.contentList = [];
+  }
+
+  let questionAttr: QuestionAttrVO = {
+    label: nameCn,
+    value: undefined,
+    nameEn: nameEn,
+  };
+
+  if (baseInfo.value.contentList.length < 1) {
+    baseInfo.value.contentList.push(questionAttr);
+  } else {
+    let existed = false;
+    for (let item of baseInfo.value.contentList) {
+      if (item.nameEn == nameEn) {
+        existed = true;
+        break;
+      }
+    }
+
+    if (!existed) {
+      baseInfo.value.contentList.push(questionAttr);
+    }
+
+  }
+}
+
+
+function handleBaseBtnDelete(nameEn: string) {
+  let contentList = [];
+  for (let item of baseInfo.value.contentList) {
+    if (item.nameEn != nameEn) {
+      contentList.push(item);
+    }
+  }
+
+  if (contentList.length < 1) {
+    baseInfo.value.questionType = undefined;
+    baseInfo.value.contentList = [];
+  } else {
+    baseInfo.value.contentList = contentList;
+  }
+}
+
+function deleteBaseInfo() {
+  baseInfo.value.contentList = [];
+  baseInfo.value.questionType = undefined;
+}
+
+function deleteOtherInfo(index: number) {
+  let newList = []
+  for (let i = 0, len = otherInfo.value.length; i < len; i++) {
+    if (i == index) {
+      continue;
+    }
+    newList.push(otherInfo.value[i]);
+  }
+  otherInfo.value = newList;
+}
+
+function handleOtherBtnDelete(index: number, childIndex: number) {
+  let other = otherInfo.value[index]
+  let newList = [];
+  for (let i = 0, len = other.contentList.length; i < len; i++) {
+    if (i == childIndex) {
+      continue;
+    }
+    newList.push(other.contentList[i]);
+  }
+  other.contentList = newList;
+
+  if (newList.length == 0) {
+    newList = [];
+    for (let i = 0, len = otherInfo.value.length; i < len; i++) {
+      if (i == index) {
+        continue;
+      }
+      newList.push(otherInfo.value[i]);
+    }
+    otherInfo.value = newList
+  }
+}
+
+function handleMatrixScaleAdd(index: number, childIndex: number, isLeft: boolean) {
+  let other = otherInfo.value[index];
+  if (other.contentList.length == childIndex + 1) {
+    other.contentList.push({
+      labelFlag: isLeft,
+      label: undefined,
+      value: undefined,
+      nameEn: 'matrix' + other.increment.toString(),
+      score: '0.0',
+      optionContent: undefined,
+      optionFlag: !isLeft  ,
+    });
+    return;
+  }
+
+  let effectiveNum = 0;
+  for (let item of other.contentList) {
+    if (isLeft) {
+      effectiveNum += (item.labelFlag ? 1 : 0);
+    } else {
+      effectiveNum += (item.optionFlag ? 1 : 0);
+    }
+  }
+
+
+  if (effectiveNum == other.contentList.length) {
+    other.contentList.push({
+      labelFlag: isLeft  ,
+      label: undefined,
+      value: undefined,
+      nameEn: 'matrix' + other.increment.toString(),
+      score: '0.0',
+      optionContent: undefined,
+      optionFlag: !isLeft  ,
+    });
+  } else {
+    let o = other.contentList[effectiveNum];
+    if (isLeft) {
+      o.labelFlag = true;
+    } else {
+      o.optionFlag = true;
+    }
+  }
+
+  for (let i = effectiveNum; i > 0; i--) {
+    let child = other.contentList[i]
+    if (i == childIndex + 1) {
+      if (isLeft) {
+        child.value = undefined;
+      } else {
+        child.optionContent = undefined;
+      }
+      break
+    }
+
+    let childPre = other.contentList[i - 1];
+    if (isLeft) {
+      child.value = childPre.value;
+    } else {
+      child.optionContent = childPre.optionContent;
+    }
+  }
+}
+
+
+function handleMatrixScaleDelete(index: number, childIndex: number, isLeft: boolean) {
+  let other = otherInfo.value[index];
+  let child = other.contentList[childIndex];
+
+  if (other.contentList.length == 0) {
+    if (isLeft) {
+      if (!child.optionFlag) {
+        other.contentList = [];
+      } else {
+        child.label = undefined;
+      }
+    } else {
+      if (!child.labelFlag) {
+        other.contentList = [];
+      } else {
+        child.optionFlag = false;
+      }
+    }
+    return;
+  }
+
+  for (let i = childIndex + 1, len = other.contentList.length; i < len; i++) {
+    let childPre = other.contentList[i - 1];
+    child = other.contentList[i];
+    if (isLeft) {
+      childPre.labelFlag = child.labelFlag;
+      childPre.value = child.value;
+    } else {
+      childPre.optionFlag = child.optionFlag;
+      childPre.optionContent = child.optionContent;
+    }
+  }
+
+  other.contentList.pop();
+}
+
+
+function handleOtherBtnAdd(index: number, questionType: string) {
+  let other = otherInfo.value[index]
+  let contentList = other.contentList;
+  other.increment += 1;
+
+  if ('singleChoice' == questionType) {
+    contentList.push({
+      label: undefined,
+      value: undefined,
+      nameEn: 'single' + other.increment.toString(),
+      allowFillBlank: false,
+      img: undefined,
+      score: '0.0',
+    });
+    return;
+  }
+
+  if ('multipleChoice' == questionType) {
+    contentList.push({
+      label: undefined,
+      value: undefined,
+      nameEn: 'multiple' + other.increment.toString(),
+      allowFillBlank: false,
+      img: undefined,
+      score: '0.0',
+    });
+    return;
+  }
+
+  if ('fillBlanks' == questionType) {
+    contentList.push({
+      label: undefined,
+      value: undefined,
+      nameEn: 'fill' + other.increment.toString(),
+      img: undefined,
+      score: '0.0',
+      unit: '',
+    });
+    return;
+  }
+
+  if ('scaleQuestions' == questionType) {
+    contentList.push({
+      label: undefined,
+      value: undefined,
+      nameEn: 'scale' + other.increment.toString(),
+      score: '0.0',
+    });
+
+    return;
+  }
+
+  if ('matrixScale' == questionType) {
+    contentList.push({
+      labelFlag: true,
+      label: undefined,
+      value: undefined,
+      nameEn: 'matrix' + other.increment.toString(),
+      score: '0.0',
+      optionContent: undefined,
+      optionFlag: true,
+    });
+
+    return;
+  }
+
+}
+
+
+
+function selectBaseInfo() {
+  baseInfo.value.selected = true;
+  otherInfo.value.forEach(item => {
+    item.selected = false;
+  });
+}
+
+function selectOtherInfo(index: number) {
+  baseInfo.value.selected = false;
+  otherInfo.value.forEach(item => {
+    item.selected = false;
+  });
+  let other = otherInfo.value[index]
+  other.selected = true;
+}
+
+
+function topUp(index: number) {
+  let other = otherInfo.value[index];
+  let newList = [other];
+  for (let i = 0, len = otherInfo.value.length; i < len; i++) {
+    if (i == index) {
+      continue;
+    }
+    newList.push(otherInfo.value[i]);
+  }
+  otherInfo.value = newList;
+}
+
+function bottomUp(index: number) {
+  let other = otherInfo.value[index];
+  let newList = [];
+  for (let i = 0, len = otherInfo.value.length; i < len; i++) {
+    if (i == index) {
+      continue;
+    }
+    newList.push(otherInfo.value[i]);
+  }
+  newList.push(other);
+  otherInfo.value = newList;
+}
+
+function moveUp(index: number) {
+  let other = otherInfo.value[index];
+  let newList = [];
+  for (let i = 0, len = otherInfo.value.length; i < len; i++) {
+    if (i == index - 1) {
+      newList.push(other);
+    }
+
+    if (i == index) {
+      continue;
+    }
+
+    newList.push(otherInfo.value[i]);
+  }
+
+  otherInfo.value = newList;
+}
+
+
+function moveDown(index: number) {
+  let other = otherInfo.value[index];
+  let newList = [];
+  for (let i = 0, len = otherInfo.value.length; i < len; i++) {
+    if (i == index) {
+      continue;
+    }
+
+    newList.push(otherInfo.value[i]);
+
+    if (i == index + 1) {
+      newList.push(other);
+    }
+  }
+
+  otherInfo.value = newList;
+}
+
+
+const bodyWidth = ref((window.document.body.clientWidth - 500) * 0.8);
+function updateBodyWidth() {
+  let leftPanel = window.document.querySelector('#leftPanel').clientWidth;
+  bodyWidth.value = (window.document.body.clientWidth - leftPanel) * 0.93;
+}
+onMounted(() => {
+  window.addEventListener('resize', updateBodyWidth);
+  updateBodyWidth();
+  getList();
+});
+onUnmounted(() => {
+  window.removeEventListener('resize', updateBodyWidth);
+});
+</script>
+
+<style scoped>
+.layout-container-demo .el-header {
+  position: relative;
+  background-color: var(--el-color-primary-light-7);
+  color: var(--el-text-color-primary);
+}
+
+.layout-container-demo .el-aside {
+  color: var(--el-text-color-primary);
+  background: var(--el-color-primary-light-8);
+}
+
+.layout-container-demo .el-menu {
+  border-right: none;
+}
+
+.layout-container-demo .el-main {
+  padding: 0;
+}
+
+.layout-container-demo .toolbar {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  height: 100%;
+  right: 20px;
+}
+
+.param-btn {
+  width: 100%;
+  height: 48px;
+  font-size: 13px;
+  font-weight: normal;
+  border: 1.5px solid #bfcbd9;
+  border-radius: 4px;
+  margin-bottom: 16px;
+  background: #fff;
+  color: #222;
+  letter-spacing: 1px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  padding: 0 2px;
+}
+
+@media (max-width: 1200px) {
+  .param-btn {
+    font-size: 13px;
+  }
+}
+
+@media (max-width: 800px) {
+  .param-btn {
+    font-size: 12px;
+  }
+}
+
+.selectedDiv {
+  background-color: #e8f4ff;
+}
+
+ul .el-upload--picture-card {
+  --el-upload-picture-card-size: 500px
+}
+
+.custom-radio-grid {
+  display: flex;
+  /* border: 1px solid #bfc4cc; */
+  border-radius: 4px;
+  background: #fff;
+  margin-bottom: 16px;
+}
+
+.custom-radio-cell {
+  width: 100px;
+  flex: none;
+  text-align: center;
+  border: 1px solid #bfc4cc;
+  margin: 0 !important;
+  height: 40px;
+  line-height: 20px;
+  font-size: 18px;
+  font-weight: normal;
+  background: #fff;
+  border-radius: 0 !important;
+  cursor: pointer;
+  user-select: none;
+  transition: background 0.2s;
+  word-break: break-all;
+  white-space: normal;
+  overflow-wrap: break-word;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0 5px;
+}
+
+.custom-radio-cell:last-child {
+  border-right: none;
+}
+
+.custom-radio-cell .el-radio__input {
+  display: none !important;
+}
+
+.custom-radio-cell .el-radio__label {
+  padding: 0;
+  width: 100%;
+  display: block;
+  text-align: center;
+  cursor: pointer;
+}
+
+.custom-radio-cell.is-checked {
+  border-right: 1px solid #bfc4cc;
+}
+
+.custom-radio-cell.is-last {
+  border-right: 1px solid #bfc4cc;
+  border-left: none;
+}
+
+
+:deep(#jffs label) {
+  font-weight: normal;
+}
+</style>

+ 286 - 0
src/views/system/screeningAssessmentConfig/index.vue

@@ -0,0 +1,286 @@
+<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="type">
+              <el-select v-model="queryParams.type" placeholder="请选择" clearable>
+                <el-option v-for="dict in scale_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
+              </el-select>
+            </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:screeningAssessmentConfig:add']">新增量表</el-button>
+          </el-col>
+          <!-- <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:screeningAssessmentConfig:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:screeningAssessmentConfig:remove']">删除</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:screeningAssessmentConfig: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="screeningAssessmentConfigList" >
+        <!-- <el-table-column type="selection" width="55" align="center" />        @selection-change="handleSelectionChange" -->
+        <el-table-column label="模板名称" align="center" prop="name" />
+        <el-table-column label="模板类型" align="center" prop="typeName" />        
+        <el-table-column label="创建人" align="center" prop="createByName" />
+        <el-table-column label="状态" align="center" width="100">
+          <template #default="scope">
+            <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
+          </template>
+        </el-table-column>
+        <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> -->
+            <el-tooltip content="编辑" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleUpdate(false,scope.row)" v-hasPermi="['system:screeningAssessmentConfig:edit']">编辑</el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:screeningAssessmentConfig:remove']">删除</el-button>
+            </el-tooltip>
+            <el-tooltip content="修改结论" placement="top">
+              <el-button link type="primary" icon="Edit" @click="handleReviseConclusion(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>
+    <!-- 添加或修改筛查/评估配置对话框 -->
+    <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
+      <el-form ref="screeningAssessmentConfigFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="性别" prop="gender">
+          <el-input v-model="form.gender" placeholder="请输入性别" />
+        </el-form-item>
+        <el-form-item label="年龄" prop="age">
+          <el-input v-model="form.age" placeholder="请输入年龄" />
+        </el-form-item>
+        <el-form-item label="量表名称" prop="name">
+          <el-input v-model="form.name" placeholder="请输入量表名称" />
+        </el-form-item>
+        <el-form-item label="量表说明" prop="description">
+          <el-input v-model="form.description" placeholder="请输入量表说明" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+            <el-input v-model="form.remark" type="textarea" 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>
+  </div>
+</template>
+
+<script setup name="ScreeningAssessmentConfig" lang="ts">
+import { listScreeningAssessmentConfig,changeConfigStatus, getScreeningAssessmentConfig, delScreeningAssessmentConfig, addScreeningAssessmentConfig, updateScreeningAssessmentConfig } from '@/api/system/screeningAssessmentConfig';
+import { ScreeningAssessmentConfigVO, ScreeningAssessmentConfigQuery, ScreeningAssessmentConfigForm } from '@/api/system/screeningAssessmentConfig/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const {scale_type} = toRefs<any>(proxy?.useDict('scale_type'));
+
+const screeningAssessmentConfigList = ref<ScreeningAssessmentConfigVO[]>([]);
+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 router = useRouter();
+
+const queryFormRef = ref<ElFormInstance>();
+const screeningAssessmentConfigFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: ScreeningAssessmentConfigForm = {
+  configId: undefined,
+  type: undefined,
+  gender: undefined,
+  age: undefined,
+  name: undefined,
+  description: undefined,
+  remark: undefined,
+  status: undefined,
+}
+const data = reactive<PageData<ScreeningAssessmentConfigForm, ScreeningAssessmentConfigQuery>>({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    type: undefined,
+    gender: undefined,
+    age: undefined,
+    name: undefined,
+    description: undefined,
+    status: undefined,
+    params: {
+    }
+  },
+  rules: {
+    configId: [
+      { required: true, message: "主键ID不能为空", trigger: "blur" }
+    ],
+    type: [
+      { required: true, message: "量表类型不能为空", trigger: "change" }
+    ],
+    name: [
+      { required: true, message: "量表名称不能为空", trigger: "blur" }
+    ],
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询筛查/评估配置列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listScreeningAssessmentConfig(queryParams.value);
+  screeningAssessmentConfigList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  screeningAssessmentConfigFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ScreeningAssessmentConfigVO[]) => {
+  ids.value = selection.map(item => item.configId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  // reset();
+  // dialog.visible = true;
+  // dialog.title = "添加筛查/评估配置";
+  router.push('/argManage/screening-assessment-page/0');
+}
+
+/** 修改按钮操作 */
+const handleUpdate = async (disabled:boolean,row?: ScreeningAssessmentConfigVO) => {
+  // reset();
+  // const _configId = row?.configId || ids.value[0]
+  // const res = await getScreeningAssessmentConfig(_configId);
+  // Object.assign(form.value, res.data);
+  // dialog.visible = true;
+  // dialog.title = "修改筛查/评估配置";
+  const _configId = row?.configId || ids.value[0]
+  if(disabled){
+    router.push('/argManage/screening-assessment-view/'+_configId);
+  }else{
+    router.push('/argManage/screening-assessment-page/'+_configId);
+  } 
+}
+
+/** 修改结论 */
+const handleReviseConclusion = async (row?: ScreeningAssessmentConfigVO) => { 
+  const _configId = row?.configId || ids.value[0]
+   
+}
+
+/** 预览 */
+const handleView = async (row?: ScreeningAssessmentConfigVO) => { 
+  const _configId = row?.configId || ids.value[0]
+   
+}
+
+
+
+/** 提交按钮 */
+const submitForm = () => {
+  screeningAssessmentConfigFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.configId) {
+        await updateScreeningAssessmentConfig(form.value).finally(() =>  buttonLoading.value = false);
+      } else {
+        await addScreeningAssessmentConfig(form.value).finally(() =>  buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功");
+      dialog.visible = false;
+      await getList();
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: ScreeningAssessmentConfigVO) => {
+  const _configIds = row?.configId || ids.value;
+  await proxy?.$modal.confirm('是否确认删除模版名称为"' + row.name + '"的数据项?').finally(() => loading.value = false);
+  await delScreeningAssessmentConfig(_configIds);
+  proxy?.$modal.msgSuccess("删除成功");
+  await getList();
+}
+ 
+
+
+const handleStatusChange = async (row: ScreeningAssessmentConfigVO) => {
+  const text = row.status === '0' ? '启用' : '停用';
+  try {
+    await proxy?.$modal.confirm('确认要"' + text + '""' + row.name + '"模版名称吗?');
+    await changeConfigStatus(row.configId, row.status);
+    proxy?.$modal.msgSuccess(text + '成功');
+  } catch {
+    row.status = row.status === '0' ? '1' : '0';
+  }
+};
+
+onMounted(() => {
+  getList();
+});
+</script>

+ 235 - 0
src/views/system/screeningAssessmentQuestion/index.vue

@@ -0,0 +1,235 @@
+<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="主键ID" prop="configId">
+              <el-input v-model="queryParams.configId" placeholder="请输入主键ID" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="标题" prop="title">
+              <el-input v-model="queryParams.title" 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" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:screeningAssessmentQuestion:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:screeningAssessmentQuestion:edit']">修改</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:screeningAssessmentQuestion:remove']">删除</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:screeningAssessmentQuestion: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="screeningAssessmentQuestionList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="主键ID" align="center" prop="questionId" v-if="true" />
+        <el-table-column label="主键ID" align="center" prop="configId" />
+        <el-table-column label="标题" align="center" prop="title" />
+        <el-table-column label="题型" align="center" prop="questionType" />
+        <el-table-column label="内容" align="center" prop="content" />
+        <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="['system:screeningAssessmentQuestion:edit']"></el-button>
+            </el-tooltip>
+            <el-tooltip content="删除" placement="top">
+              <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:screeningAssessmentQuestion: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="screeningAssessmentQuestionFormRef" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="主键ID" prop="configId">
+          <el-input v-model="form.configId" placeholder="请输入主键ID" />
+        </el-form-item>
+        <el-form-item label="标题" prop="title">
+            <el-input v-model="form.title" type="textarea" placeholder="请输入内容" />
+        </el-form-item>
+        <el-form-item label="内容">
+          <editor v-model="form.content" :min-height="192"/>
+        </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>
+  </div>
+</template>
+
+<script setup name="ScreeningAssessmentQuestion" lang="ts">
+import { listScreeningAssessmentQuestion, getScreeningAssessmentQuestion, delScreeningAssessmentQuestion, addScreeningAssessmentQuestion, updateScreeningAssessmentQuestion } from '@/api/system/screeningAssessmentQuestion';
+import { ScreeningAssessmentQuestionVO, ScreeningAssessmentQuestionQuery, ScreeningAssessmentQuestionForm } from '@/api/system/screeningAssessmentQuestion/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const screeningAssessmentQuestionList = ref<ScreeningAssessmentQuestionVO[]>([]);
+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 queryFormRef = ref<ElFormInstance>();
+const screeningAssessmentQuestionFormRef = ref<ElFormInstance>();
+
+const dialog = reactive<DialogOption>({
+  visible: false,
+  title: ''
+});
+
+const initFormData: ScreeningAssessmentQuestionForm = {
+  questionId: undefined,
+  configId: undefined,
+  title: undefined,
+  questionType: undefined,
+  content: undefined,
+}
+const data = reactive<PageData<ScreeningAssessmentQuestionForm, ScreeningAssessmentQuestionQuery>>({
+  form: {...initFormData},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    configId: undefined,
+    title: undefined,
+    questionType: undefined,
+    content: undefined,
+    params: {
+    }
+  },
+  rules: {
+    questionId: [
+      { required: true, message: "主键ID不能为空", trigger: "blur" }
+    ],
+    title: [
+      { required: true, message: "标题不能为空", trigger: "blur" }
+    ],
+    questionType: [
+      { required: true, message: "题型不能为空", trigger: "change" }
+    ],
+  }
+});
+
+const { queryParams, form, rules } = toRefs(data);
+
+/** 查询题目列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listScreeningAssessmentQuestion(queryParams.value);
+  screeningAssessmentQuestionList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+}
+
+/** 取消按钮 */
+const cancel = () => {
+  reset();
+  dialog.visible = false;
+}
+
+/** 表单重置 */
+const reset = () => {
+  form.value = {...initFormData};
+  screeningAssessmentQuestionFormRef.value?.resetFields();
+}
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+}
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ScreeningAssessmentQuestionVO[]) => {
+  ids.value = selection.map(item => item.questionId);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+}
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  reset();
+  dialog.visible = true;
+  dialog.title = "添加题目";
+}
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: ScreeningAssessmentQuestionVO) => {
+  reset();
+  const _questionId = row?.questionId || ids.value[0]
+  const res = await getScreeningAssessmentQuestion(_questionId);
+  Object.assign(form.value, res.data);
+  dialog.visible = true;
+  dialog.title = "修改题目";
+}
+
+/** 提交按钮 */
+const submitForm = () => {
+  screeningAssessmentQuestionFormRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      if (form.value.questionId) {
+        await updateScreeningAssessmentQuestion(form.value).finally(() =>  buttonLoading.value = false);
+      } else {
+        await addScreeningAssessmentQuestion(form.value).finally(() =>  buttonLoading.value = false);
+      }
+      proxy?.$modal.msgSuccess("操作成功");
+      dialog.visible = false;
+      await getList();
+    }
+  });
+}
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: ScreeningAssessmentQuestionVO) => {
+  const _questionIds = row?.questionId || ids.value;
+  await proxy?.$modal.confirm('是否确认删除题目编号为"' + _questionIds + '"的数据项?').finally(() => loading.value = false);
+  await delScreeningAssessmentQuestion(_questionIds);
+  proxy?.$modal.msgSuccess("删除成功");
+  await getList();
+}
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download('system/screeningAssessmentQuestion/export', {
+    ...queryParams.value
+  }, `screeningAssessmentQuestion_${new Date().getTime()}.xlsx`)
+}
+
+onMounted(() => {
+  getList();
+});
+</script>

+ 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), '')