3 Commits d8712ade66 ... 2db54b8718

Tác giả SHA1 Thông báo Ngày
  ystl_myq 2db54b8718 feat: 考核模板、考核管理 4 tháng trước cách đây
  haifeng.zhang 93381af544 feat: 指标数据 5 tháng trước cách đây
  haifeng.zhang d52a1d3f05 feat: 指标数据开发中 5 tháng trước cách đây

+ 16 - 0
src/api/assessment.ts

@@ -30,6 +30,16 @@ export const getAssessmentObjectDetails = params => {
     }
   );
 };
+// 修改考核信息详情(考核指标)
+export const postUpdateAssessmentQuotaDetails = data => {
+  return http.request<addDeptList>(
+    "post",
+    "/assessment/updateAssessmentQuotaDetails",
+    {
+      data
+    }
+  );
+};
 // 考核详情
 export const getAssessmentDetails = params => {
   return http.request<addDept>("get", "/assessment/getAssessmentDetails", {
@@ -48,6 +58,12 @@ export const delAssessmentObject = data => {
     data
   });
 };
+// 考核指标批量删除考核指标相关信息
+export const delBatchDelScoreInfoVO = data => {
+  return http.request<addDept>("post", "/assessment/batchDelScoreInfoVO", {
+    data
+  });
+};
 // 考核信息详情(考核指标)
 export const getAssessmentQuotaDetails = params => {
   return http.request<addDeptList>(

+ 50 - 0
src/api/indexData.ts

@@ -0,0 +1,50 @@
+/*
+ * @Author: zhanghaifeng
+ * @Date: 2025-01-06 17:11:11
+ * @LastEditors: zhanghaifeng
+ * @LastEditTime: 2025-01-08 11:04:59
+ * @Description:
+ * @FilePath: /hospital-project/src/api/indexData.ts
+ */
+import { http } from "@/utils/http";
+
+type PageListType = {
+  code: number;
+  msg: string;
+  data: recordsList;
+};
+type recordsList = {
+  totalRow: number;
+  records: Array<any>;
+};
+type PageListItemDelType = {
+  code: number;
+  msg: string;
+  data: boolean;
+};
+
+// 分页查询
+export const getPageList = data => {
+  return http.request<PageListType>("post", "/source/pageSourceData", { data });
+};
+
+// 分页-删除
+export const delListItemApi = params => {
+  return http.request<PageListItemDelType>("get", "/source/deleteSourceData", {
+    params
+  });
+};
+
+// 分页-新增
+export const addListApi = data => {
+  return http.request<PageListItemDelType>("post", "/source/addSourceData", {
+    data
+  });
+};
+
+// 分页-编辑
+export const editListApi = data => {
+  return http.request<PageListItemDelType>("post", "/source/updateSourceData", {
+    data
+  });
+};

+ 733 - 0
src/components/formula/manyFormula.vue

@@ -0,0 +1,733 @@
+<script setup lang="ts">
+import { ref, reactive, watch, onMounted, nextTick } from "vue";
+import { ElMessageBox, ElMessage } from "element-plus";
+// import { calculator } from "./evaluate";
+import {
+  getIndexInfo,
+  postUpdate,
+  calculateScoreByConditionMoCondition
+} from "@/api/templateInfo";
+import { conditionVerify } from "@/api/formula";
+import jishuanqi from "@/views/evaluate/children/change/components/jishuanqi.vue";
+import {
+  postListTreeWithUser,
+  postListTree,
+  postListTreeWithUserApi,
+  treeDept
+} from "@/api/department";
+import type { DrawerProps, FormItemProps, FormProps } from "element-plus";
+const $props = defineProps({
+  formulaJSON: {
+    type: Object
+  }
+});
+onMounted(() => {
+  if ($props.formulaJSON) {
+    nextTick(() => {
+      Object.assign(addmanyChange, JSON.parse($props.formulaJSON));
+      console.log("多公式", addmanyChange);
+    });
+    // let a = JSON.parse($props.formulaJSON);
+  }
+});
+const $emit = defineEmits(["handUpdataClick"]);
+const keywords = [
+  "目标值",
+  "完成值",
+  "挑战值",
+  "门槛值",
+  "增幅",
+  "降幅",
+  "上期完成值",
+  "上上期完成值"
+];
+const addmanyChange = reactive({
+  outerConditionValue: "",
+  outerConditionValueExpress: "",
+  innerConditionExpression: [
+    {
+      startValue: null,
+      endValue: null,
+      select: null,
+      comparisonStart: null,
+      comparisonEnd: null,
+      innerScore: null,
+      innerScoreExpress: null,
+      scoreRuleMoreInnerVO: [
+        // 内部公式类型
+        // {
+        //   innerConditionValue: null,
+        //   scoreRules: [
+        //     {
+        //       score: null,
+        //       startValue: null,
+        //       select: "",
+        //       endValue: null,
+        //       comparisonStart: null,
+        //       comparisonEnd: null
+        //     }
+        //   ]
+        // }
+      ]
+    }
+  ]
+});
+const jishuanqiRef = ref();
+const countNoConditionFormula = item => {
+  addmanyChange.outerConditionValue = item;
+  changeAddMay();
+};
+const countNoConditionFormula1 = (item, index) => {
+  addmanyChange.innerConditionExpression[index].endValue = item;
+  changeAddMay();
+};
+const countNoConditionFormula2 = (item, index) => {
+  // innerConditionValue
+  addmanyChange.innerConditionExpression[
+    index
+  ].scoreRuleMoreInnerVO[0].innerConditionValue = item;
+  changeAddMay();
+};
+const countNoConditionFormula3 = (item, index, indexList) => {
+  addmanyChange.innerConditionExpression[
+    index
+  ].scoreRuleMoreInnerVO[0].scoreRules[indexList].endValue = item;
+  changeAddMay();
+};
+const countNoConditionFormula4 = (item, index, indexList) => {
+  addmanyChange.innerConditionExpression[
+    index
+  ].scoreRuleMoreInnerVO[0].scoreRules[indexList].score = item;
+  changeAddMay();
+};
+const countNoConditionFormulaInnerScore = (item, index, indexList) => {
+  addmanyChange.innerConditionExpression[index].innerScore = item;
+  changeAddMay();
+};
+const changeAddMay = () => {
+  let addmanyChangeOldVal = JSON.parse(JSON.stringify(addmanyChange));
+  addmanyChange.innerConditionExpression.forEach((item, index, arr) => {
+    if (index + 1 <= arr.length) {
+      if (item.endValue) {
+        if (addmanyChangeOldVal.innerConditionExpression.length > 1) {
+          addmanyChangeOldVal.innerConditionExpression[index + 1].startValue =
+            item?.endValue;
+          addmanyChangeOldVal.innerConditionExpression[index + 1].startExpress =
+            item?.endExpress;
+          addmanyChangeOldVal.innerConditionExpression[
+            index + 1
+          ].comparisonStart = item?.comparisonEnd == "≥" ? ">" : "≥";
+        }
+      }
+      if (item.scoreRuleMoreInnerVO.length > 0) {
+        if (item.scoreRuleMoreInnerVO[0].scoreRules.length > 0) {
+          item.scoreRuleMoreInnerVO[0].scoreRules.map((it, id, arrChild) => {
+            if (id + 1 <= arrChild.length) {
+              if (it.endValue) {
+                // if (
+                //   addmanyChangeOldVal.innerConditionExpression[index + 1]
+                //     .scoreRuleMoreInnerVO.length > 0
+                // ) {
+                addmanyChangeOldVal.innerConditionExpression[
+                  index
+                ].scoreRuleMoreInnerVO[0].scoreRules[id + 1].startValue =
+                  it?.endValue;
+                addmanyChangeOldVal.innerConditionExpression[
+                  index
+                ].scoreRuleMoreInnerVO[0].scoreRules[id + 1].startExpress =
+                  it?.startExpress;
+                addmanyChangeOldVal.innerConditionExpression[
+                  index
+                ].scoreRuleMoreInnerVO[0].scoreRules[id + 1].comparisonStart =
+                  it?.comparisonEnd == "≥" ? ">" : "≥";
+                // }
+              }
+            }
+          });
+        }
+      }
+    }
+  });
+  Object.assign(addmanyChange, addmanyChangeOldVal);
+  $emit("handUpdataClick", addmanyChange);
+};
+const deleteItem = index => {
+  addmanyChange.innerConditionExpression.splice(index, 1);
+  changeAddMay();
+};
+const dataleListItemAll = (item, index) => {
+  item.scoreRuleMoreInnerVO = [];
+  changeAddMay();
+};
+const dataleListItem = (index, indexList) => {
+  addmanyChange.innerConditionExpression[
+    index
+  ].scoreRuleMoreInnerVO[0].scoreRules.splice(indexList, 1);
+  changeAddMay();
+};
+const addItemDataList = (
+  index: number,
+  itemList: any,
+  indexList: number,
+  item
+) => {
+  addmanyChange.innerConditionExpression[
+    index
+  ].scoreRuleMoreInnerVO[0].scoreRules.push({
+    select: null,
+    score: null,
+    scoreExpress: null,
+    startValue: itemList.endValue,
+    endValue: null,
+    comparisonStart: itemList.comparisonEnd,
+    comparisonEnd: null
+  });
+  addmanyChange.innerConditionExpression[index].innerScore = "";
+  changeAddMay();
+};
+// 添加子条件
+const addItemFormulaList = (item, index) => {
+  addmanyChange.innerConditionExpression[index].scoreRuleMoreInnerVO = [
+    {
+      innerConditionValue: null,
+      innerConditionValueExpress: null,
+      scoreRules: [
+        {
+          score: null,
+          scoreExpress: null,
+          startValue: null,
+          startExpress: null,
+          endValue: null,
+          endExpress: null,
+          select: null,
+          comparisonStart: null,
+          comparisonEnd: null
+        }
+      ]
+    }
+  ];
+  addmanyChange.innerConditionExpression[index].innerScore = "";
+  changeAddMay();
+};
+const addNewItemOldValue = item => {
+  addmanyChange.innerConditionExpression.push({
+    startValue: item.endValue,
+    select: null,
+    endValue: null,
+    comparisonStart: item.comparisonEnd == ">" ? "≥" : ">",
+    comparisonEnd: null,
+    scoreRuleMoreInnerVO: []
+  });
+  changeAddMay();
+};
+const addNewItem = () => {
+  addmanyChange.innerConditionExpression.push({
+    startValue: null,
+    endValue: null,
+    select: null,
+    comparisonStart: null,
+    comparisonEnd: null,
+    scoreRuleMoreInnerVO: []
+  });
+  changeAddMay();
+};
+function extractKeywordsAndPush(keywordsList, inputString) {
+  keywords.forEach(keyword => {
+    if (
+      inputString?.includes(keyword) && // 检查字符串是否包含关键字
+      !formulaForm.value.some(item => item.name === keyword) // 确保没有重复
+    ) {
+      // 如果包含且未重复,构造对象并push到formulaForm.value
+      formulaForm.value.push({
+        name: keyword,
+        value: null // 根据需求赋值
+      });
+    }
+  });
+
+  return formulaForm;
+}
+// 公式验证
+const grade = ref();
+const formulaForm = ref([]);
+const dialogFormVisibleFormula = ref(false);
+const manyConditions = () => {
+  grade.value = null;
+  dialogFormVisibleFormula.value = true;
+  formulaForm.value = [];
+  // 提取 addmanyChange 对象中的条件值
+  let oldValue = addmanyChange;
+  if (oldValue.outerConditionValue) {
+    extractKeywordsAndPush(keywords, oldValue.outerConditionValue);
+  }
+  if (oldValue.innerConditionExpression.length > 0) {
+    oldValue.innerConditionExpression.map(itemExp => {
+      if (itemExp.startValue) {
+        extractKeywordsAndPush(keywords, itemExp.startValue);
+      }
+      if (itemExp.endValue) {
+        extractKeywordsAndPush(keywords, itemExp.endValue);
+      }
+      if (itemExp.innerScore) {
+        extractKeywordsAndPush(keywords, itemExp.innerScore);
+      }
+      if (itemExp?.scoreRuleMoreInnerVO[0]?.innerConditionValue) {
+        extractKeywordsAndPush(
+          keywords,
+          itemExp.scoreRuleMoreInnerVO[0].innerConditionValue
+        );
+      }
+      if (itemExp?.scoreRuleMoreInnerVO.length > 0) {
+        if (itemExp?.scoreRuleMoreInnerVO[0]?.scoreRules.length > 0) {
+          itemExp?.scoreRuleMoreInnerVO[0]?.scoreRules.map(itSc => {
+            if (itSc.score) {
+              extractKeywordsAndPush(keywords, itSc.score);
+            }
+            if (itSc.startValue) {
+              extractKeywordsAndPush(keywords, itSc.startValue);
+            }
+            if (itSc.endValue) {
+              extractKeywordsAndPush(keywords, itSc.endValue);
+            }
+          });
+        }
+      }
+    });
+  }
+};
+function calculateScore(inputString) {
+  // 将 formulaForm.value 转换为一个键值对映射,方便替换
+  const valueMap = formulaForm.value.reduce((map, item) => {
+    map[item.name] = item.value !== null ? item.value : 0; // 用 0 替代 null
+    return map;
+  }, {});
+
+  // 替换字符串中的关键字为对应的 value 值
+  let formula = inputString;
+  for (const [key, value] of Object.entries(valueMap)) {
+    if (formula.includes(key)) {
+      // 使用正则替换所有匹配的关键字
+      const regex = new RegExp(key, "g");
+      formula = formula.replace(regex, value);
+    }
+  }
+
+  try {
+    // 使用 eval 计算公式
+    const result = eval(formula);
+    return result;
+  } catch (error) {
+    console.error("公式计算出错:", error);
+    return null;
+  }
+}
+// 取消计算
+const quxiaoCmputed = () => {
+  formulaForm.value = [];
+  dialogFormVisibleFormula.value = false;
+};
+const countComputed = async () => {
+  try {
+    let oldValue = JSON.parse(JSON.stringify(addmanyChange));
+    // Object.assign(oldValue, addmanyChange);
+    const formulaPar = JSON.stringify(oldValue);
+    const res = await conditionVerify({
+      formulaType: 1,
+      formula: formulaPar
+    });
+    if (res.code == 200) {
+      if (oldValue.outerConditionValue) {
+        oldValue.outerConditionValue = calculateScore(
+          oldValue.outerConditionValue
+        );
+      }
+      if (oldValue.innerConditionExpression.length > 0) {
+        oldValue.innerConditionExpression.map(itEx => {
+          if (itEx.startValue) {
+            itEx.startValue = calculateScore(itEx.startValue);
+          }
+          if (itEx.endValue) {
+            itEx.endValue = calculateScore(itEx.endValue);
+          }
+          if (itEx.innerScore) {
+            itEx.innerScore = calculateScore(itEx.innerScore);
+          }
+          if (itEx?.scoreRuleMoreInnerVO[0]?.innerConditionValue) {
+            itEx.scoreRuleMoreInnerVO[0].innerConditionValue = calculateScore(
+              itEx.scoreRuleMoreInnerVO[0].innerConditionValue
+            );
+          }
+          if (itEx?.scoreRuleMoreInnerVO[0]?.scoreRules.length > 0) {
+            itEx.scoreRuleMoreInnerVO[0].scoreRules.map(itRule => {
+              if (itRule.score) {
+                itRule.score = calculateScore(itRule.score);
+              }
+              if (itRule.startValue) {
+                itRule.startValue = calculateScore(itRule.startValue);
+              }
+              if (itRule.endValue) {
+                itRule.endValue = calculateScore(itRule.endValue);
+              }
+            });
+          }
+        });
+      }
+      // let newValue = reverseReplace(formListNum, oldValue);
+      const { code, msg, data } =
+        await calculateScoreByConditionMoCondition(oldValue);
+      if (code == 200) {
+        grade.value = data;
+      }
+    } else {
+      ElMessageBox.confirm(
+        "如果不修改将无法计算得分",
+        "公式计算错误,修改后确认",
+        {
+          type: "warning"
+        }
+      ).then(() => {
+        dialogFormVisibleFormula.value = false;
+      });
+    }
+  } catch (err) {
+    console.log(err);
+  }
+};
+</script>
+
+<template>
+  <div class="w-full">
+    <div class="flex items-center">
+      <el-text>条件值 = </el-text>
+      <jishuanqi
+        v-if="addmanyChange.outerConditionValue"
+        ref="jishuanqiRef"
+        :outerConditionValue="addmanyChange.outerConditionValue"
+        @handClick="countNoConditionFormula"
+      />
+    </div>
+    <div class="w-full mt-2">
+      <div
+        v-for="(item, index) in addmanyChange.innerConditionExpression"
+        :key="index"
+      >
+        <div class="mt-3">
+          <div class="flex">
+            <el-text class="w-[15px]" type="primary">
+              {{ index + 1 }}
+            </el-text>
+            <div class="w-[40px] ml-4">
+              {{ item.startValue }}
+            </div>
+            <div class="mr-2">
+              {{ item.comparisonStart }}
+            </div>
+            <div class="mr-2">条件值</div>
+            <el-select
+              v-model="item.comparisonEnd"
+              style="width: 60px"
+              placeholder=""
+              :disabled="
+                index + 1 == addmanyChange.innerConditionExpression.length &&
+                index >= 1
+                  ? true
+                  : false
+              "
+            >
+              <el-option label=">" value=">" />
+              <el-option label="≥" value="≥" />
+            </el-select>
+            <el-select
+              v-model="item.select"
+              class="ml-2"
+              style="width: 80px"
+              placeholder=""
+              :disabled="
+                index + 1 == addmanyChange.innerConditionExpression.length &&
+                index >= 1
+                  ? true
+                  : false
+              "
+            >
+              <el-option label="数值" value="数值" />
+              <el-option label="公式" value="公式" />
+            </el-select>
+            <el-input
+              v-if="
+                index + 1 == addmanyChange.innerConditionExpression.length &&
+                index >= 1
+              "
+              disabled
+              class="ml-2 mr-5"
+              style="width: 80px"
+            />
+            <div v-else>
+              <jishuanqi
+                v-if="item.select == '公式'"
+                ref="jishuanqiRef"
+                :index="index"
+                :outerConditionValue="item.endValue"
+                class="ml-2 mr-5"
+                style="width: 80px"
+                @handClick="countNoConditionFormula1"
+              />
+              <el-input
+                v-else
+                v-model="item.endValue"
+                class="ml-2 mr-5"
+                style="width: 80px"
+              />
+            </div>
+            <el-text v-if="item.scoreRuleMoreInnerVO.length == 0"
+              >得分 =
+            </el-text>
+            <div>
+              <jishuanqi
+                v-if="item.scoreRuleMoreInnerVO.length == 0"
+                ref="jishuanqiRef"
+                :index="index"
+                :outerConditionValue="item.innerScore"
+                class="ml-2 mr-6"
+                style="width: 100px"
+                @handClick="countNoConditionFormulaInnerScore"
+              />
+            </div>
+            <el-text type="danger" @click="deleteItem(index)">
+              <el-icon>
+                <Delete />
+              </el-icon>
+            </el-text>
+          </div>
+          <div
+            v-for="(itemVO, indexOV) in item.scoreRuleMoreInnerVO"
+            :key="indexOV"
+          >
+            <div class="flex mt-2 ml-20">
+              <div class="mr-2">条件值{{ index + 1 }} =</div>
+              <jishuanqi
+                ref="jishuanqiRef"
+                :index="index"
+                :outerConditionValue="itemVO.innerConditionValue"
+                class="ml-2 mr-6"
+                style="width: 208px"
+                @handClick="countNoConditionFormula2"
+              />
+              <!-- <el-input v-model="itemVO.innerConditionValue" class="ml-2 mr-6" style="width: 208px" /> -->
+              <el-text type="danger" @click="dataleListItemAll(item, index)">
+                <el-icon>
+                  <Delete />
+                </el-icon>
+              </el-text>
+            </div>
+            <div
+              v-for="(itemList, indexList) in itemVO.scoreRules"
+              :key="indexList"
+              class="ml-3"
+            >
+              <div class="flex mt-2 ml-16 items-center">
+                <div class="text_border">{{ indexList + 1 }}</div>
+                <!-- manyChange.dataList[index].dataList -->
+                <div class="w-[40px] ml-2">
+                  {{ itemList.startValue }}
+                </div>
+                <div class="mr-2">
+                  {{ itemList.comparisonStart }}
+                </div>
+                <div class="mr-2">条件值{{ index + 1 }}</div>
+                <el-select
+                  v-model="itemList.comparisonEnd"
+                  style="width: 60px"
+                  placeholder=""
+                  :disabled="
+                    indexList + 1 == itemVO.scoreRules.length ? true : false
+                  "
+                >
+                  <el-option label=">" value=">" />
+                  <el-option label="≥" value="≥" />
+                </el-select>
+                <el-select
+                  v-model="itemList.select"
+                  class="ml-2"
+                  style="width: 80px"
+                  placeholder=""
+                  :disabled="
+                    indexList + 1 == itemVO.scoreRules.length ? true : false
+                  "
+                >
+                  <el-option label="数值" value="数值" />
+                  <el-option label="公式" value="公式" />
+                </el-select>
+                <el-input
+                  v-if="indexList + 1 == itemVO.scoreRules.length"
+                  disabled
+                  class="ml-2"
+                  style="width: 80px"
+                />
+                <div v-else>
+                  <jishuanqi
+                    v-if="itemList.select == '公式'"
+                    ref="jishuanqiRef"
+                    :index="index"
+                    :indexList="indexList"
+                    :outerConditionValue="itemList.endValue"
+                    class="ml-2"
+                    style="width: 80px"
+                    @handClick="countNoConditionFormula3"
+                  />
+                  <el-input
+                    v-else
+                    v-model="itemList.endValue"
+                    class="ml-2"
+                    style="width: 80px"
+                  />
+                </div>
+                <div
+                  class="w-[150px] ml-2 mr-2 flex justify-between items-center"
+                >
+                  <div>得分</div>
+                  <div>=</div>
+                  <!-- <el-input v-if="
+                                  indexList + 1 == itemVO.scoreRules.length &&
+                                  index >= 1
+                                " disabled class="ml-2" style="width: 80px" /> -->
+                  <jishuanqi
+                    ref="jishuanqiRef"
+                    :index="index"
+                    :indexList="indexList"
+                    :outerConditionValue="itemList.score"
+                    class="ml-2"
+                    style="width: 80px"
+                    @handClick="countNoConditionFormula4"
+                  />
+                  <!-- <el-input
+                                  v-model="itemList.score"
+                                  class="ml-2"
+                                  style="width: 80px"
+                                /> -->
+                </div>
+                <el-text
+                  type="danger"
+                  @click="dataleListItem(index, indexList)"
+                >
+                  <el-icon>
+                    <Delete />
+                  </el-icon>
+                </el-text>
+              </div>
+              <div
+                v-if="indexList + 1 == itemVO.scoreRules.length"
+                class="ml-16 mt-2 text-xs cursor-pointer"
+              >
+                <el-text
+                  type="primary"
+                  @click="addItemDataList(index, itemList, indexList, item)"
+                >
+                  <el-icon>
+                    <Plus />
+                  </el-icon>
+                  添加子条件
+                </el-text>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div
+          v-if="item?.scoreRuleMoreInnerVO?.length == 0"
+          class="ml-20 mt-2 text-xs cursor-pointer"
+        >
+          <el-text type="primary" @click="addItemFormulaList(item, index)">
+            <el-icon>
+              <Plus />
+            </el-icon>
+            添加子条件
+          </el-text>
+        </div>
+        <div
+          v-if="
+            addmanyChange.innerConditionExpression.length >= 1 &&
+            index + 1 == addmanyChange.innerConditionExpression.length
+          "
+          class="mt-5 text-xs cursor-pointer"
+        >
+          <el-text type="primary" @click="addNewItemOldValue(item)">
+            <el-icon>
+              <Plus />
+            </el-icon>
+            添加条件
+          </el-text>
+        </div>
+      </div>
+      <div
+        v-if="addmanyChange.innerConditionExpression.length < 1"
+        class="mt-5 text-xs cursor-pointer"
+      >
+        <el-text type="primary" @click="addNewItem">
+          <el-icon>
+            <Plus />
+          </el-icon>
+          添加条件
+        </el-text>
+      </div>
+      <div class="float-right mr-8">
+        <el-button type="primary" @click="manyConditions">公式验证</el-button>
+      </div>
+    </div>
+    <!-- 公式验证 -->
+    <el-dialog
+      v-model="dialogFormVisibleFormula"
+      title="完成值"
+      width="500"
+      :before-close="quxiaoCmputed"
+    >
+      <el-form :model="formulaForm">
+        <div v-for="(itemF, indexF) in formulaForm" :key="indexF">
+          <el-form-item :label="itemF.name">
+            <el-input v-model="itemF.value" autocomplete="off" />
+          </el-form-item>
+        </div>
+        <el-form-item label="得分">
+          <el-input v-model="grade" class="ml-3" autocomplete="off" disabled />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="quxiaoCmputed">取消</el-button>
+          <el-button type="primary" @click="countComputed"> 计算 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.compute {
+  // border: 1px solid #f3f3f3;
+  background-color: #f3f3f3;
+  border-radius: 5px;
+}
+
+.text-color {
+  color: #f3f3f3;
+}
+
+.areYouOK {
+  background-color: #409eff;
+}
+
+.bgBack {
+  background-color: #f3f3f3;
+}
+
+.text_border {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 14px;
+  height: 14px;
+  // margin-top: 5px;
+  margin-right: 5px;
+  font-size: 12px;
+  border: 1px solid gray;
+  border-radius: 50%;
+}
+</style>

+ 9 - 1
src/main.ts

@@ -1,3 +1,11 @@
+/*
+ * @Author: zhanghaifeng
+ * @Date: 2025-01-03 09:09:02
+ * @LastEditors: zhanghaifeng
+ * @LastEditTime: 2025-01-06 14:59:51
+ * @Description:
+ * @FilePath: /hospital-project/src/main.ts
+ */
 import App from "./App.vue";
 import router from "./router";
 import { setupStore } from "@/store";
@@ -12,7 +20,7 @@ import * as ElementPlusIconsVue from "@element-plus/icons-vue";
 import Table from "@pureadmin/table";
 // import PureDescriptions from "@pureadmin/descriptions";
 // 控制台被动监听警告
-import "default-passive-events";
+// import "default-passive-events";
 // 引入重置样式
 import "./style/reset.scss";
 // 导入公共样式

+ 19 - 0
src/routerList/index.ts

@@ -1,3 +1,11 @@
+/*
+ * @Author: zhanghaifeng
+ * @Date: 2024-11-27 15:44:12
+ * @LastEditors: zhanghaifeng
+ * @LastEditTime: 2025-01-03 14:05:55
+ * @Description:
+ * @FilePath: /hospital-project/src/routerList/index.ts
+ */
 // 最简代码,也就是这些字段必须有
 import server from "@/assets/icon-png/menuList/server (1).svg";
 import menuIndex from "@/assets/svg/layers.svg?component";
@@ -28,6 +36,17 @@ export default {
             // 是否继承父菜单显示,默认false
             showParent: true
           }
+        },
+        {
+          path: "/indexData/index",
+          name: "IndexData",
+          component: () => import("@/views/indexData/index.vue"),
+          meta: {
+            auths: [],
+            title: "指标数据",
+            // 是否继承父菜单显示,默认false
+            showParent: true
+          }
         }
       ]
     }

+ 12 - 0
src/views/background/framework/roles/rolePower.vue

@@ -96,6 +96,18 @@ const postPageRoleApi = async () => {
 // 查看角色组
 const bgColor = ref(null);
 onMounted(async () => {
+  // 用来手动调用接口新增权限
+  // postAddMenuList({
+  //   menuName: "指标数据",
+  //   menuType: "menu",
+  //   parentCode: "menu340262294435012608",
+  //   orderNum: 1,
+  //   url: "",
+  //   moduleUrl: "/indexData/index",
+  //   icon: "",
+  //   remar: "",
+  //   menuCode: ""
+  // });
   // 低网模式 等待 postPageRoleApi 完成
   await postPageRoleApi();
   // setTimeout(() => {

+ 161 - 0
src/views/evaluate/children/change/components/editMould.vue

@@ -7,6 +7,7 @@ import {
   getDimensionRemove,
   postUpdateDept
 } from "@/api/dimension";
+import { postUpdate } from "@/api/templateInfo";
 import type { DrawerProps, FormItemProps, FormProps } from "element-plus";
 // const itemLabelPosition = ref<FormItemProps["labelPosition"]>("");
 const drawer = ref(false);
@@ -37,9 +38,12 @@ const formLabelAlign = reactive({
   showFinalValue: 0,
   showChallengeValue: 0,
   showStartValue: 0,
+  showRemark: 0,
   remark: "",
   tpId: ""
 });
+// 存储指标变量,修改指标最高值时使用
+const changeIndexList = ref([]);
 const newAddItem = ref();
 const handleClose = (done: () => void) => {
   done();
@@ -53,6 +57,7 @@ function confirmClick() {
     if (valid) {
       if (dalongTitle.value == "编辑") {
         postUpdateDeptApi();
+        postUpdateApi();
       } else {
         postAddDimensionApi();
       }
@@ -60,7 +65,9 @@ function confirmClick() {
   });
 }
 const titleName = ref("");
+const indexList = ref([]);
 const open = (row: any, title: string, order: number) => {
+  console.log("编辑", row, title);
   dalongTitle.value = title;
   if (title == "新建") {
     titleName.value = "新建考核维度";
@@ -77,6 +84,7 @@ const open = (row: any, title: string, order: number) => {
       showFinalValue: 0,
       showChallengeValue: 0,
       showStartValue: 0,
+      showRemark: 0,
       remark: "",
       order: order
     });
@@ -87,6 +95,31 @@ const open = (row: any, title: string, order: number) => {
       ...row,
       order
     });
+    formLabelAlign.tableData.map(item => {
+      item["indNameIndex"] = item.indName;
+    });
+    changeIndexList.value = formLabelAlign.tableData;
+    indexList.value = formLabelAlign.tableData.reduce((acc, current) => {
+      // 找到当前项的 indLimitScore 是否已经存在于结果数组中
+      let found = acc.find(
+        group => group[0].indLimitScore === current.indLimitScore
+      );
+      if (found) {
+        // 如果找到了,添加当前项到对应的数组中
+        found.push(current);
+      } else {
+        // 如果没有找到,创建一个新的数组并将当前项加入
+        acc.push([current]);
+      }
+
+      return acc;
+    }, []);
+    // indexList.value.map(item => {
+    //   item.map(it => {
+    //     it["indNameIndex"] = "";
+    //   });
+    // });
+    watchTableData();
   }
   drawer.value = true;
 };
@@ -138,6 +171,12 @@ const showStartValueValue = computed({
     formLabelAlign.showStartValue = value ? 1 : 0;
   }
 });
+const remarkValueValue = computed({
+  get: () => formLabelAlign.showRemark === 1,
+  set: value => {
+    formLabelAlign.showRemark = value ? 1 : 0;
+  }
+});
 // 分割————————————————————————————————————————————
 // 新建维度
 const postAddDimensionApi = async () => {
@@ -174,9 +213,70 @@ const postUpdateDeptApi = async () => {
     });
   }
 };
+const handClose = (indName, ind, indId) => {
+  // console.log("item", ind, indId);
+  indexList.value[ind].splice(indId, 1);
+  if (indexList.value[ind].length == 0) {
+    indexList.value.splice(ind, 1);
+  }
+  let newIndName = indName;
+  newIndName.indLimitScore = null;
+  formLabelAlign.tableData.push(newIndName);
+};
+const delteIndex = (index, item) => {
+  indexList.value[index].map(it => {
+    it.indLimitScore = null;
+    formLabelAlign.tableData.push(it);
+  });
+  indexList.value.splice(index, 1);
+};
+const addIndex = () => {
+  indexList.value.push([
+    {
+      indNameIndex: "",
+      indLimitScore: null
+    }
+  ]);
+};
+const watchTableData = () => {
+  const idsToDelete = new Set();
+  // 通过 indexList 删除 tableData 中相同 id 的数据
+  // 迭代 indexList 中的每个 id
+  indexList.value.forEach(group => {
+    group.forEach(item => {
+      idsToDelete.add(item.id);
+    });
+  });
+  const filteredTableData = formLabelAlign.tableData.filter(
+    item => !idsToDelete.has(item.id)
+  );
+  formLabelAlign.tableData = filteredTableData;
+  console.log("tableDatatableData", filteredTableData);
+};
+const handChangeIndex = (ind, val) => {
+  changeIndexList.value.map(item => {
+    if (item.id == val) {
+      formLabelAlign.tableData;
+      indexList.value[ind].push(item);
+      watchTableData();
+    }
+  });
+};
+const postUpdateApi = async () => {
+  indexList.value.map(item => {
+    item.map(async (item1, index1, arr1) => {
+      if (item1.id) {
+        item1.indLimitScore = arr1[item.length - 1].indLimitScore;
+        const { code } = await postUpdate(item1);
+      }
+    });
+  });
+  console.log("indexList", indexList.value);
+};
 defineExpose({
   open
 });
+const value = ref("");
 </script>
 
 <template>
@@ -228,6 +328,63 @@ defineExpose({
                 <el-radio :value="1" size="large">加和</el-radio>
               </el-radio-group>
             </el-form-item>
+            <el-form-item
+              v-if="dalongTitle == '编辑'"
+              label="指标最高分限制"
+              label-position="top"
+            >
+              <div>
+                <div v-for="(int, ind) in indexList" :key="ind" class="mt-2">
+                  <el-text
+                    class="mx-1"
+                    type="danger"
+                    @click="delteIndex(ind, int)"
+                    ><el-icon> <Delete /> </el-icon
+                  ></el-text>
+                  <el-text class="mx-1">不超过</el-text>
+                  <el-input
+                    v-model="int[int.length - 1].indLimitScore"
+                    size="small"
+                    style="width: 30px"
+                  />
+                  <el-text class="mx-1">分</el-text>
+                  <!-- <el-select v-model="int[int.length - 1].indNameIndex" placeholder="Select" style="width: 140px" -->
+                  <el-select
+                    v-model="int[int.length - 1].indNameIndex"
+                    placeholder="Select"
+                    style="width: 140px"
+                    filterable
+                    @change="handChangeIndex(ind, $event)"
+                  >
+                    <el-option
+                      v-for="item in formLabelAlign.tableData"
+                      :key="item.id"
+                      :label="item.indName"
+                      :value="item.id"
+                    />
+                  </el-select>
+                  <span v-for="(indName, indId) in int" :key="indId">
+                    <el-tag
+                      v-if="indName.indName"
+                      type="info"
+                      class="ml-2"
+                      closable
+                      @close="handClose(indName, ind, indId)"
+                    >
+                      {{ indName.indName }}
+                    </el-tag>
+                  </span>
+                </div>
+                <!-- <el-text size="large" type="primary">
+                  <el-icon>
+                    <Plus />
+                  </el-icon>
+                </el-text> -->
+                <el-tag type="primary" @click="addIndex">
+                  <el-icon> <Plus /> </el-icon>添加
+                </el-tag>
+              </div>
+            </el-form-item>
             <el-form-item label="指标显示字段设置" label-position="top">
               <template #default>
                 <div>
@@ -259,6 +416,10 @@ defineExpose({
                     <el-text type="info">门槛值</el-text>
                     <el-switch v-model="showStartValueValue" class="ml-2" />
                   </div>
+                  <div class="ml-3">
+                    <el-text type="info">备注</el-text>
+                    <el-switch v-model="remarkValueValue" class="ml-2" />
+                  </div>
                 </div>
               </template>
             </el-form-item>

+ 1 - 1
src/views/evaluate/children/change/components/jishuanqi.vue

@@ -198,7 +198,7 @@
 <script setup lang="ts">
 import { reactive, ref, onMounted } from "vue";
 const dropdown = ref();
-const $emit = defineEmits(["handClick", "clickIndex"]);
+const $emit = defineEmits(["handClick"]);
 const $props = defineProps({
   index: {
     type: Number

+ 29 - 2
src/views/evaluate/children/change/components/newAdd.vue

@@ -58,12 +58,15 @@ const handleSelect = index => {
 const tepName = ref();
 const tepNameForm = reactive({
   tpName: "",
-  id: ""
+  id: "",
+  isScoreConverse: 1
 });
 onMounted(() => {
   if (route.query.tpName && route.query.id) {
     tepNameForm.tpName = route.query.tpName;
     tepNameForm.id = route.query.id;
+    console.log("route.query", route.query);
+    tepNameForm.isScoreConverse = Number(route.query.isScoreConverse);
   }
 });
 const tableData = ref([]);
@@ -82,6 +85,7 @@ const postAddTemplateApi = async () => {
     });
     tepNameForm.tpName = res.data.tpName;
     tepNameForm.id = res.data.id;
+    tepNameForm.isScoreConverse = res.data.isScoreConverse;
     titleShow.value = true;
   } else {
     ElMessage.error(res.msg);
@@ -183,7 +187,8 @@ const backChange = () => {
 const postUpdateTemplateInApi = async () => {
   const { code, msg } = await postUpdateTemplateIn({
     tpName: tepNameForm.tpName,
-    id: tepNameForm.id
+    id: tepNameForm.id,
+    isScoreConverse: tepNameForm.isScoreConverse
   });
   if (code == 200) {
     titleShow.value = true;
@@ -486,6 +491,22 @@ const titleShowClick = () => {
               placeholder="最多输入100字"
             />
           </el-form-item>
+          <el-form-item
+            prop="isScoreConverse"
+            label="是否支持得分换算"
+            :rules="[
+              {
+                required: true,
+                message: '是否支持得分换算',
+                trigger: 'blur'
+              }
+            ]"
+          >
+            <el-radio-group v-model="tepNameForm.isScoreConverse">
+              <el-radio :value="1" size="large">是</el-radio>
+              <el-radio :value="0" size="large">否</el-radio>
+            </el-radio-group>
+          </el-form-item>
         </el-form>
       </div>
       <div v-else class="w-[90%] m-auto mt-4">
@@ -615,6 +636,12 @@ const titleShowClick = () => {
                 title="门槛值"
                 :edit-render="{ name: 'input' }"
               />
+              <vxe-column
+                v-if="item.showRRemark"
+                field="remark"
+                title="备注"
+                :edit-render="{ name: 'input' }"
+              />
               <vxe-column field="age" fixed="right" title="操作">
                 <template #default="{ row }">
                   <el-icon class="mr-3" @click="settingIndex(row)">

+ 7 - 0
src/views/evaluate/children/change/components/settingIndexDrawer.vue

@@ -68,6 +68,7 @@ const params = reactive({
   startValue: "",
   datasoure: "",
   weight: "",
+  valueScore: 0,
   scoreValue: "",
   formula: {
     noConditionFormula: ""
@@ -938,6 +939,12 @@ const formulaFocus = () => {
                 @check-change="handleRreeSelect()"
               />
             </el-form-item>
+            <el-form-item label="完成值计算方式" label-position="top">
+              <el-radio-group v-model="params.valueScore">
+                <el-radio :value="0" size="large">统计计算</el-radio>
+                <el-radio :value="1" size="large">累加计算</el-radio>
+              </el-radio-group>
+            </el-form-item>
             <el-form-item label="评分方式" label-position="top">
               <el-select v-model="params.scoreStandard" placeholder="请选择">
                 <el-option label="手动输入" :value="0" />

+ 349 - 0
src/views/evaluate/children/change/mould/editIndex.vue

@@ -0,0 +1,349 @@
+<script setup lang="ts">
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import type { DrawerProps, FormProps } from "element-plus";
+import { editListApi } from "@/api/indexData";
+import dayjs from "dayjs";
+// getIndexInfo,
+// postUpdate,
+import { postUpdateAssessmentQuotaDetails } from "@/api/assessment";
+import { conditionVerify } from "@/api/formula";
+import jishuanqi from "@/views/evaluate/children/change/components/jishuanqi.vue";
+import manyFormula from "@/components/formula/manyFormula.vue";
+const emit = defineEmits(["updateDialog"]);
+const formRef = ref();
+const drawer = ref(false);
+const disabledShow = ref<any>(true);
+const direction = ref<DrawerProps["direction"]>("rtl");
+// 表单
+const formulaOne = ref();
+const formLabelAlign = reactive({
+  relationId: "",
+  targetValue: 0,
+  finalValue: 0,
+  addValue: 0,
+  decValue: 0,
+  challengeValue: 0,
+  startValue: 0,
+  upperValue: 0,
+  agupperValue: 0,
+  dataSource: "",
+  score: 0,
+  assessmentObjectId: "",
+  assessmentModelId: "",
+  id: "",
+  dimId: "",
+  assessmentId: "",
+  formulaType: 0,
+  formula: ""
+});
+// 抽屉逻辑
+const open = row => {
+  console.log("编辑", row);
+  Object.assign(formLabelAlign, row);
+  // console.log(1111, JSON.parse(formLabelAlign.formula));
+  let dataJSon = JSON.parse(row.formula);
+  formulaOne.value = dataJSon.noConditionFormula;
+  // if (formLabelAlign.formulaType == 0) {
+  // } else {
+  //   formula
+  // }
+  drawer.value = true;
+};
+// 无条件
+const countNoConditionFormulaClick = (data: any) => {
+  formulaOne.value = data;
+};
+defineExpose({
+  open
+});
+const handleClose = (done: () => void) => {
+  if (disabledShow.value) {
+    disabledShow.value = true;
+    drawer.value = false;
+  } else {
+    ElMessageBox.confirm("配置项未保存,确认关闭", {
+      type: "warning"
+    }).then(() => {
+      formRef.value.clearValidate(); // 清除验证错误
+      formRef.value.resetFields(); // 重置表单字段
+      disabledShow.value = true;
+      drawer.value = false;
+    });
+  }
+};
+const cancelClick = () => {
+  formRef.value.validate(valid => {
+    if (valid) {
+      disabledShow.value = true;
+    }
+  });
+};
+// 公式验证
+const dialogFormVisibleFormula = ref(false);
+const formulaForm = ref([]);
+const grade = ref();
+const conditionVerifyApi = async () => {
+  grade.value = null;
+  formulaForm.value = [];
+  dialogFormVisibleFormula.value = true;
+  try {
+    formulaForm.value = [];
+    grade.value = "";
+    const keywords = [
+      "完成值",
+      "目标值",
+      "门槛值",
+      "挑战值",
+      "增幅",
+      "降幅",
+      "上期完成值",
+      "上上期完成值"
+    ];
+    let str = formulaOne.value;
+    // 创建一个正则表达式,匹配这些关键词
+    const regex = new RegExp(keywords.join("|"), "g");
+    // 提取字符串中的所有关键词
+    let matches = str.match(regex);
+    matches.forEach(matchItem => {
+      formulaForm.value.push({
+        name: matchItem,
+        value: ""
+      });
+    });
+    let result = str.replace(regex, match => {
+      return `${match}`;
+    });
+  } catch (err) {
+    //
+  }
+};
+// 取消计算
+const quxiaoCmputed = () => {
+  formulaForm.value = [];
+  dialogFormVisibleFormula.value = false;
+};
+const countComputed = async () => {
+  try {
+    if (formLabelAlign.formulaType == 0) {
+      const formula = JSON.stringify({
+        noConditionFormula: formulaOne.value
+      });
+      // const aa = JSON.stringify(formula);
+      // console.log("formula", aa);
+      const { code, msg } = await conditionVerify({
+        formulaType: formLabelAlign.formulaType,
+        formula
+      });
+      if (code == 200) {
+        const keywords = [
+          "完成值",
+          "目标值",
+          "门槛值",
+          "挑战值",
+          "增幅",
+          "降幅",
+          "上期完成值",
+          "上上期完成值"
+        ];
+        let str = formulaOne.value;
+        // 创建一个正则表达式,匹配这些关键词
+        const regex = new RegExp(keywords.join("|"), "g");
+        let result = str.replace(regex, match => {
+          // 在 formulaForm.value 中查找匹配的项
+          let matchedItem = formulaForm.value.find(item => item.name === match);
+
+          // 如果找到匹配项,返回其 value,否则返回原始匹配值
+          if (matchedItem) {
+            return matchedItem.value; // 替换为对应的值
+          }
+          return match; // 如果没有匹配项,返回原始匹配值
+        });
+        // calculator.calculate(result);
+        // grade.value = calculator.get();
+        grade.value = eval(result).toFixed(2);
+      } else {
+        ElMessageBox.confirm(
+          "如果不修改将无法计算得分",
+          "公式计算错误,修改后确认",
+          {
+            type: "warning"
+          }
+        ).then(() => {
+          dialogFormVisibleFormula.value = false;
+        });
+      }
+    }
+  } catch (err) {
+    console.log(err);
+  }
+};
+// 确认
+const confirmClick = () => {
+  postUpdateApi();
+};
+// 更新模板指标关联
+const postUpdateApi = async () => {
+  let params = {};
+  Object.assign(params, formLabelAlign);
+  params.formula = JSON.stringify({
+    noConditionFormula: formulaOne.value
+  });
+  // if (formLabelAlign.formulaType ==0) {
+  // }
+  // else if (formLabelAlign.formulaType == 1) {
+  // }
+  const { code, msg } = await postUpdateAssessmentQuotaDetails(params);
+  if (code === 200) {
+    ElMessage.success("修改成功");
+    drawer.value = false;
+  } else {
+    // ElMessage.error(msg);
+  }
+};
+</script>
+
+<template>
+  <div>
+    <el-drawer
+      v-model="drawer"
+      :direction="direction"
+      :before-close="handleClose"
+      size="700px"
+    >
+      <template #header>
+        <h4>公式设置</h4>
+      </template>
+      <template #default>
+        <div>
+          <el-form
+            ref="formRef"
+            label-position="top"
+            label-width="auto"
+            :model="formLabelAlign"
+          >
+            <el-form-item
+              label="考核对象"
+              label-position="top"
+              prop="sourceValue"
+            >
+              <el-input
+                v-model="formLabelAlign.assessmentObjectName"
+                disabled
+              />
+            </el-form-item>
+            <el-form-item
+              label="考核模板"
+              label-position="top"
+              prop="sourceValue"
+            >
+              <el-input v-model="formLabelAlign.assessmentModelName" disabled />
+            </el-form-item>
+            <el-form-item
+              label="指标名称"
+              label-position="top"
+              prop="sourceValue"
+            >
+              <el-input v-model="formLabelAlign.name" disabled />
+            </el-form-item>
+            <el-form-item
+              label="完成值录入人"
+              label-position="top"
+              prop="sourceValue"
+            >
+              <el-select v-model="formLabelAlign.valueInput" disabled>
+                <el-option label="被考核人" :value="0" />
+                <!-- <el-option label="上级" :value="1" /> -->
+                <el-option label="指定人员" :value="2" />
+                <el-option label="考核管理员" :value="3" />
+                <el-option label="指标自动采集" :value="4" />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              label="评分方式"
+              label-position="top"
+              prop="sourceValue"
+            >
+              <el-select v-model="formLabelAlign.scoreStandard" disabled>
+                <el-option label="手动输入" :value="0" />
+                <el-option label="公式自动计算" :value="1" />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              label="公式类型"
+              label-position="top"
+              prop="sourceValue"
+            >
+              <el-select v-model="formLabelAlign.formulaType">
+                <el-option label="无条件公式" :value="0" />
+                <el-option label="多条件公式" :value="1" />
+              </el-select>
+            </el-form-item>
+            <el-form-item
+              v-if="formLabelAlign.formulaType == 0"
+              label="设置"
+              label-position="top"
+              prop="sourceValue"
+            >
+              <div class="w-full flex item-center">
+                <div class="mr-4"><el-text>得分 =</el-text></div>
+                <jishuanqi
+                  :outerConditionValue="formulaOne"
+                  @handClick="countNoConditionFormulaClick"
+                />
+                <div class="ml-8">
+                  <el-button type="primary" @click="conditionVerifyApi">
+                    公式验证
+                  </el-button>
+                </div>
+              </div>
+            </el-form-item>
+            <el-form-item
+              v-else
+              label="设置"
+              label-position="top"
+              prop="sourceValue"
+            >
+              <div class="w-full flex item-center">
+                <manyFormula
+                  :formulaJSON="formulaOne"
+                  @handUpdataClick="countNoConditionFormulaClick"
+                />
+              </div>
+            </el-form-item>
+          </el-form>
+        </div>
+      </template>
+      <template #footer>
+        <div>
+          <el-button @click="cancelClick">取消</el-button>
+          <el-button type="primary" @click="confirmClick">确认</el-button>
+        </div>
+      </template>
+    </el-drawer>
+    <!-- 公式验证 -->
+    <el-dialog
+      v-model="dialogFormVisibleFormula"
+      title="完成值"
+      width="500"
+      :before-close="quxiaoCmputed"
+    >
+      <el-form :model="formulaForm">
+        <div v-for="(itemF, indexF) in formulaForm" :key="indexF">
+          <el-form-item :label="itemF.name">
+            <el-input v-model="itemF.value" autocomplete="off" />
+          </el-form-item>
+        </div>
+        <el-form-item label="得分">
+          <el-input v-model="grade" class="ml-3" autocomplete="off" disabled />
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="quxiaoCmputed">取消</el-button>
+          <el-button type="primary" @click="countComputed"> 计算 </el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>

+ 67 - 1
src/views/evaluate/children/change/mould/manageObject.vue

@@ -13,8 +13,10 @@ import {
   alterFinishValue,
   automaticCollection,
   setTableHeader,
+  delBatchDelScoreInfoVO,
   getAssessmentDetails
 } from "@/api/assessment";
+import editIndex from "./editIndex.vue";
 import { getTemplateInfoList } from "@/api/templateInfo";
 import { useRouter } from "vue-router";
 import { ElMessage, ElMessageBox } from "element-plus";
@@ -1111,10 +1113,65 @@ const hideColEvent = field => {
     $table.hideColumn(field);
   }
 };
+const editIndexShow = ref(null);
+const editIndexOpen = (row: Object) => {
+  editIndexShow.value.open({
+    ...row,
+    assessmentId: initParams.indexParams.assessmentId
+  });
+};
+const deleteParam = ref([]);
+// 批量删除指标
+const deleteMantList = () => {
+  if (deleteParam.value.length === 0) {
+    ElMessage.warning("请选择要删除的考核指标");
+  } else {
+    ElMessageBox.confirm(
+      "这些考核项删除后将不可恢复,请谨慎操作",
+      "确定删除这些考核项吗?",
+      {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }
+    ).then(async () => {
+      deleteParam.value.map(item => {
+        item.assessmentId = initParams.indexParams.assessmentId;
+      });
+      const { code } = await delBatchDelScoreInfoVO(deleteParam.value);
+      if (code === 200) {
+        getAssessmentQuotaDetailsApi();
+        activeName.value = "second";
+        ElMessage({
+          type: "success",
+          message: "删除成功"
+        });
+      }
+    });
+  }
+};
+// 单选
+const selectChangeEvent = row => {
+  const $table = tableVxeRef.value;
+  deleteParam.value = [];
+  if ($table) {
+    deleteParam.value = row.records;
+  }
+};
+// 全选
+const selectAllChangeEvent = row => {
+  const $table = tableVxeRef.value;
+  deleteParam.value = [];
+  if ($table) {
+    deleteParam.value = row.records;
+  }
+};
 </script>
 
 <template>
   <div class="w-full">
+    <!-- 指标编辑 -->
+    <editIndex ref="editIndexShow" />
     <div class="w-full flex items-center justify-between">
       <div class="flex items-center justify-between mt-2">
         <div class="bg-icon">
@@ -1293,6 +1350,9 @@ const hideColEvent = field => {
             >
           </div> -->
           <div class="mr-10 flex items-center justify-between">
+            <el-button type="primary" plain @click="deleteMantList"
+              >批量删除</el-button
+            >
             <el-button type="primary" plain @click="automaticCollectionApi"
               >同步最新数据</el-button
             >
@@ -1312,6 +1372,8 @@ const hideColEvent = field => {
           :edit-rules="validRules"
           :data="initParams.Indexlist"
           @edit-closed="editClosedEvent"
+          @checkbox-change="selectChangeEvent"
+          @checkbox-all="selectAllChangeEvent"
           @selection-change="changeSelection"
           @edit-disabled="editDisabledEvent"
         >
@@ -1621,7 +1683,11 @@ const hideColEvent = field => {
                 </template>
               </el-dropdown>
             </template>
-            <template #default> - </template>
+            <template #default="{ row }">
+              <el-icon @click="editIndexOpen(row)">
+                <Edit />
+              </el-icon>
+            </template>
           </vxe-column>
         </vxe-table>
         <div class="flex justify-between item-center">

+ 253 - 0
src/views/indexData/components/addDialog.vue

@@ -0,0 +1,253 @@
+<script setup lang="ts">
+import { reactive, ref } from "vue";
+import { ElMessage } from "element-plus";
+import { postListTree } from "@/api/department";
+import { getQuotaPageList } from "@/api/indexDefine";
+import { addListApi } from "@/api/indexData";
+import { postOrganizationUserPage } from "@/api/userSetting";
+import dayjs from "dayjs";
+
+// 表单状态
+const ruleFormRef = ref(null);
+const form = reactive({
+  sourceValue: "",
+  deptCode: "",
+  deptId: "",
+  deptName: "",
+  userId: "",
+  userName: "",
+  numerator: "",
+  denominator: "",
+  dataValue: "",
+  specificTime: ""
+});
+const rules = reactive({
+  sourceValue: [{ required: true, message: "请输入指标名称", trigger: "blur" }],
+  deptCode: [{ required: true, message: "请选择部门名称", trigger: "change" }],
+  specificTime: [{ required: true, message: "请选择日期", trigger: "change" }]
+});
+
+// 弹窗逻辑
+const emit = defineEmits(["updateDialog"]);
+const visible = ref(false);
+const open = () => {
+  // 打开弹框
+  Object.assign(form, {
+    sourceValue: "",
+    deptCode: "",
+    deptId: "",
+    deptName: "",
+    userId: "",
+    userName: "",
+    numerator: "",
+    denominator: "",
+    dataValue: "",
+    specificTime: ""
+  });
+  visible.value = true;
+  getQuotaPageListApi();
+  getIndustryListApi();
+};
+defineExpose({
+  open
+});
+const close = () => {
+  visible.value = false;
+  ruleFormRef.value.resetFields();
+};
+
+// 指标名称
+const restaurants = ref([]);
+const querySearch = (queryString: string, cb: any) => {
+  const results = queryString
+    ? restaurants.value.filter(createFilter(queryString))
+    : restaurants.value;
+  cb(results);
+};
+const createFilter = (queryString: string) => {
+  return restaurant => {
+    return restaurant.name.indexOf(queryString) === 0;
+  };
+};
+const getQuotaPageListApi = async () => {
+  const { code, data, msg } = await getQuotaPageList({
+    pageNumber: 1,
+    pageSize: 99999,
+    orderField: "updateTime",
+    param: null,
+    categoryName: null,
+    statue: null,
+    orderType: null
+  });
+  if (code == 200) {
+    if (data.records && data.records.length) {
+      restaurants.value = data.records;
+    } else {
+      restaurants.value = [];
+    }
+  } else {
+    ElMessage.error(msg);
+  }
+};
+
+// 部门名称
+const defaultProps = {
+  children: "childrenRes",
+  label: "deptName",
+  value: "deptCode"
+};
+const deptSelect = ref([]);
+const getIndustryListApi = async () => {
+  const { code, data } = await postListTree();
+  if (code == 200) {
+    deptSelect.value = data || [];
+  }
+};
+const handleNodeDeptClick = data => {
+  form.deptId = data.hospitalCode;
+  form.deptName = data.deptName;
+};
+
+// 员工名称
+const userSelect = ref([]);
+const handleDept = async val => {
+  const { code, data, msg } = await postOrganizationUserPage({
+    pageNumber: 1,
+    pageSize: 99999,
+    organizationCode: val,
+    organizationType: "dept",
+    realName: ""
+  });
+  if (code == 200) {
+    userSelect.value = data.records;
+  } else {
+    ElMessage.error(msg);
+  }
+};
+const handleUser = val => {
+  userSelect.value.forEach(item => {
+    if (item.hospitalCode === val) {
+      form.userName = item.realName;
+    }
+  });
+};
+
+// 保存
+const addSave = async () => {
+  ruleFormRef.value.validate(async (valid: boolean) => {
+    if (valid) {
+      if (form.specificTime) {
+        form.specificTime = dayjs(form.specificTime).format("YYYY-MM-DD");
+      }
+      const { code, msg } = await addListApi(form);
+      if (code === 200) {
+        ElMessage({
+          message: "添加成功",
+          type: "success"
+        });
+        close();
+        emit("updateDialog");
+      } else {
+        ElMessage({
+          message: msg,
+          type: "error"
+        });
+      }
+    }
+  });
+};
+</script>
+
+<template>
+  <el-dialog
+    v-model="visible"
+    title="添加数据"
+    width="480"
+    :close-on-click-modal="false"
+    :before-close="close"
+  >
+    <div class="m-auto w-full">
+      <el-form
+        ref="ruleFormRef"
+        :model="form"
+        label-position="right"
+        label-width="auto"
+        :rules="rules"
+        style="max-width: 600px"
+      >
+        <el-form-item label="指标名称" prop="sourceValue">
+          <el-autocomplete
+            v-model="form.sourceValue"
+            value-key="name"
+            :fetch-suggestions="querySearch"
+            :trigger-on-focus="false"
+            clearable
+            placeholder="请输入"
+            style="width: 100%"
+          />
+        </el-form-item>
+        <el-form-item label="部门名称" prop="deptCode">
+          <el-tree-select
+            v-model="form.deptCode"
+            :data="deptSelect"
+            :props="defaultProps"
+            default-expand-all
+            check-strictly
+            :render-after-expand="false"
+            @change="handleDept"
+            @node-click="handleNodeDeptClick"
+          />
+        </el-form-item>
+        <el-form-item label="员工名称">
+          <el-select
+            v-model="form.userId"
+            filterable
+            placeholder="请选择"
+            @change="handleUser"
+          >
+            <el-option
+              v-for="(item, index) in userSelect"
+              :key="index"
+              :label="item.realName"
+              :value="item.hospitalCode"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="分子">
+          <el-input
+            v-model="form.numerator"
+            autocomplete="off"
+            placeholder="请输入"
+          />
+        </el-form-item>
+        <el-form-item label="分母">
+          <el-input
+            v-model="form.denominator"
+            autocomplete="off"
+            placeholder="请输入"
+          />
+        </el-form-item>
+        <el-form-item label="值">
+          <el-input
+            v-model="form.dataValue"
+            autocomplete="off"
+            placeholder="请输入"
+          />
+        </el-form-item>
+        <el-form-item label="数据时间" prop="specificTime">
+          <el-date-picker
+            v-model="form.specificTime"
+            type="date"
+            placeholder="请选择日期"
+          />
+        </el-form-item>
+      </el-form>
+    </div>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="close">取消</el-button>
+        <el-button type="primary" @click="addSave">保存</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>

+ 293 - 0
src/views/indexData/components/editDrawer.vue

@@ -0,0 +1,293 @@
+<script setup lang="ts">
+import { ref, reactive, onMounted } from "vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+import type { DrawerProps, FormProps } from "element-plus";
+import { getQuotaPageList } from "@/api/indexDefine";
+import { editListApi } from "@/api/indexData";
+import { postListTree } from "@/api/department";
+import { postOrganizationUserPage } from "@/api/userSetting";
+import dayjs from "dayjs";
+
+const emit = defineEmits(["updateDialog"]);
+const formRef = ref();
+const drawer = ref(false);
+const disabledShow = ref<any>(true);
+const direction = ref<DrawerProps["direction"]>("rtl");
+
+// 表单
+const formLabelAlign = reactive({
+  id: "",
+  sourceValue: "",
+  deptCode: "",
+  deptId: "",
+  deptName: "",
+  userId: "",
+  userName: "",
+  numerator: "",
+  denominator: "",
+  dataValue: "",
+  specificTime: ""
+});
+const rules = reactive<FormProps["rules"]>({
+  sourceValue: [{ required: true, message: "请输入指标名称", trigger: "blur" }],
+  define: { required: true, message: "请输入指标定义", trigger: "blur" },
+  caliber: { required: true, message: "请输入指标口径", trigger: "blur" },
+  statue: { required: true, message: "请选择指标状态", trigger: "blur" }
+});
+
+// 抽屉逻辑
+const open = row => {
+  getQuotaPageListApi();
+  getIndustryListApi();
+  Object.assign(formLabelAlign, row);
+  if (row.deptCode) {
+    handleDept(row.deptCode);
+  }
+  drawer.value = true;
+};
+defineExpose({
+  open
+});
+const handleClose = (done: () => void) => {
+  if (disabledShow.value) {
+    disabledShow.value = true;
+    drawer.value = false;
+  } else {
+    ElMessageBox.confirm("配置项未保存,确认关闭", {
+      type: "warning"
+    }).then(() => {
+      formRef.value.clearValidate(); // 清除验证错误
+      formRef.value.resetFields(); // 重置表单字段
+      disabledShow.value = true;
+      drawer.value = false;
+    });
+  }
+};
+const cancelClick = () => {
+  formRef.value.validate(valid => {
+    if (valid) {
+      disabledShow.value = true;
+    }
+  });
+};
+const editClick = () => {
+  disabledShow.value = false;
+};
+
+// 确认
+const confirmClick = () => {
+  formRef.value.validate(async valid => {
+    if (valid) {
+      if (formLabelAlign.specificTime) {
+        formLabelAlign.specificTime = dayjs(formLabelAlign.specificTime).format(
+          "YYYY-MM-DD"
+        );
+      }
+      const { code, msg } = await editListApi(formLabelAlign);
+      if (code === 200) {
+        ElMessage({
+          message: "修改成功",
+          type: "success"
+        });
+        disabledShow.value = true;
+        drawer.value = false;
+        emit("updateDialog");
+      } else {
+        ElMessage.error(msg);
+      }
+    }
+  });
+};
+
+// 指标名称
+const restaurants = ref([]);
+const querySearch = (queryString: string, cb: any) => {
+  const results = queryString
+    ? restaurants.value.filter(createFilter(queryString))
+    : restaurants.value;
+  cb(results);
+};
+const createFilter = (queryString: string) => {
+  return restaurant => {
+    return restaurant.name.indexOf(queryString) === 0;
+  };
+};
+const getQuotaPageListApi = async () => {
+  const { code, data, msg } = await getQuotaPageList({
+    pageNumber: 1,
+    pageSize: 99999,
+    orderField: "updateTime",
+    param: null,
+    categoryName: null,
+    statue: null,
+    orderType: null
+  });
+  if (code == 200) {
+    if (data.records && data.records.length) {
+      restaurants.value = data.records;
+    } else {
+      restaurants.value = [];
+    }
+  } else {
+    ElMessage.error(msg);
+  }
+};
+
+// 部门名称
+const defaultProps = {
+  children: "childrenRes",
+  label: "deptName",
+  value: "deptCode"
+};
+const deptSelect = ref([]);
+const getIndustryListApi = async () => {
+  const { code, data } = await postListTree();
+  if (code == 200) {
+    deptSelect.value = data || [];
+  }
+};
+const handleNodeDeptClick = data => {
+  formLabelAlign.deptId = data.hospitalCode;
+  formLabelAlign.deptName = data.deptName;
+};
+
+// 员工名称
+const userSelect = ref([]);
+const handleDept = async val => {
+  const { code, data, msg } = await postOrganizationUserPage({
+    pageNumber: 1,
+    pageSize: 99999,
+    organizationCode: val,
+    organizationType: "dept",
+    realName: ""
+  });
+  if (code == 200) {
+    userSelect.value = data.records;
+  } else {
+    ElMessage.error(msg);
+  }
+};
+const handleUser = val => {
+  userSelect.value.forEach(item => {
+    if (item.hospitalCode === val) {
+      formLabelAlign.userName = item.realName;
+    }
+  });
+};
+</script>
+
+<template>
+  <div>
+    <el-drawer
+      v-model="drawer"
+      :direction="direction"
+      :before-close="handleClose"
+    >
+      <template #header>
+        <h4>数据详情</h4>
+      </template>
+      <template #default>
+        <div>
+          <el-form
+            ref="formRef"
+            label-position="top"
+            label-width="auto"
+            :rules="rules"
+            :model="formLabelAlign"
+          >
+            <el-form-item
+              label="指标名称"
+              label-position="top"
+              prop="sourceValue"
+            >
+              <el-autocomplete
+                v-model="formLabelAlign.sourceValue"
+                value-key="name"
+                :fetch-suggestions="querySearch"
+                :trigger-on-focus="false"
+                clearable
+                placeholder="请输入"
+                :disabled="disabledShow"
+                style="width: 100%"
+              />
+            </el-form-item>
+            <el-form-item label="部门名称" label-position="top" prop="deptCode">
+              <el-tree-select
+                v-model="formLabelAlign.deptCode"
+                :disabled="disabledShow"
+                :data="deptSelect"
+                :props="defaultProps"
+                default-expand-all
+                check-strictly
+                :render-after-expand="false"
+                @change="handleDept"
+                @node-click="handleNodeDeptClick"
+              />
+            </el-form-item>
+            <el-form-item label="员工名称" label-position="top">
+              <el-select
+                v-model="formLabelAlign.userId"
+                :disabled="disabledShow"
+                filterable
+                placeholder="请选择"
+                @change="handleUser"
+              >
+                <el-option
+                  v-for="(item, index) in userSelect"
+                  :key="index"
+                  :label="item.realName"
+                  :value="item.hospitalCode"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="分子" label-position="top">
+              <el-input
+                v-model="formLabelAlign.numerator"
+                :disabled="disabledShow"
+                autocomplete="off"
+                placeholder="请输入"
+              />
+            </el-form-item>
+            <el-form-item label="分母" label-position="top">
+              <el-input
+                v-model="formLabelAlign.denominator"
+                :disabled="disabledShow"
+                autocomplete="off"
+                placeholder="请输入"
+              />
+            </el-form-item>
+            <el-form-item label="值" label-position="top">
+              <el-input
+                v-model="formLabelAlign.dataValue"
+                :disabled="disabledShow"
+                autocomplete="off"
+                placeholder="请输入"
+              />
+            </el-form-item>
+            <el-form-item
+              label="数据时间"
+              prop="specificTime"
+              label-position="top"
+            >
+              <el-date-picker
+                v-model="formLabelAlign.specificTime"
+                :disabled="disabledShow"
+                type="date"
+                placeholder="请选择日期"
+              />
+            </el-form-item>
+          </el-form>
+        </div>
+      </template>
+      <template #footer>
+        <el-button v-if="disabledShow" type="warning" @click="editClick"
+          >编辑</el-button
+        >
+        <div v-else>
+          <el-button @click="cancelClick">取消</el-button>
+          <el-button type="primary" @click="confirmClick">确认</el-button>
+        </div>
+      </template>
+    </el-drawer>
+  </div>
+</template>

+ 163 - 0
src/views/indexData/components/indexImport.vue

@@ -0,0 +1,163 @@
+<script setup lang="ts">
+defineOptions({
+  name: "IndexDefineImport"
+});
+import { ref } from "vue";
+import { postUpdateDeptApi, getListTable } from "@/api/download";
+import { ElMessage } from "element-plus";
+import { useRouter } from "vue-router";
+const $router = useRouter();
+const emit = defineEmits(["handleImport"]);
+const uploadShow = ref(true);
+const fileDocument = ref<File | null>(null); // 存储文件
+const backupUrl =
+  import.meta.env.VITE_BACKUP_URL + "/download/指标导入模板.xlsx";
+// 上传文件函数
+// 上传文件函数
+const uploadFile = async () => {
+  if (!fileDocument.value) {
+    ElMessage.error("请先上传文件");
+    return;
+  }
+
+  try {
+    const formData = new FormData();
+    // 将生成的文件添加到 FormData 中
+    formData.append("file", fileDocument.value);
+    // 获取文件并执行上传
+    const fileToSend = formData.get("file");
+    // 调用 API 上传文件
+    const { data } = await postUpdateDeptApi({ file: fileToSend });
+    // const { data } = await postUpdateDeptApi({ file: formData });
+    if (data.code === 200) {
+      // 根据后端返回的 code 判断是否成功
+      ElMessage.success("文件上传并导入成功");
+      // 重置上传状态
+      backDefine();
+      $router.push("/indexDefine/children/define");
+      tableHeaders.value = []; // 清空表头
+    } else {
+      ElMessage.error(data.msg);
+    }
+  } catch (error) {
+    ElMessage.error("上传失败,请重试");
+    console.error(error);
+  }
+};
+
+const tableHeaders = ref<any>([]);
+const tableData = ref<any>([]);
+
+const backDefine = () => {
+  emit("handleImport");
+};
+
+// 处理文件上传并解析 Excel 内容
+const handleUploadChange = async (file: File) => {
+  fileDocument.value = file; // 保存文件
+  const reader = new FileReader();
+  let fileType = file.name.slice(-4);
+  if (fileType == "xlsx") {
+    const formData = new FormData();
+    formData.append("file", fileDocument.value); // 将文件添加到表单数据
+    // 调用 API 上传文件
+    const { data } = await getListTable(formData);
+    if (data.code === 200) {
+      tableData.value = data.data;
+    }
+    uploadShow.value = false; // 隐藏上传区域,显示数据表
+  } else {
+    ElMessage.error("请上传xlsx文件");
+  }
+
+  reader.readAsArrayBuffer(file); // 读取文件为 ArrayBuffer
+};
+
+const stateDone = state => {
+  const arr = ["草稿", "发布", "下架"];
+  return arr[state];
+};
+</script>
+
+<template>
+  <div>
+    <div class="w-3/4 m-auto mt-4">
+      <div class="w-full bg p-3">
+        <h5>1.下载导入模板</h5>
+        <p class="text-xs mt-2 text">根据提升信息完善表格内容</p>
+        <el-button class="mt-2">
+          <el-icon> <Download /> </el-icon
+          ><a :href="backupUrl" download="指标导入模板.xlsx"
+            >下载空的模板表格</a
+          >
+        </el-button>
+      </div>
+      <div class="w-full mt-4 bg p-3">
+        <h5>2.上传完善后的模板</h5>
+        <p class="text-xs mt-2 text mb-2">
+          更新信息的模板中,如有空的数据列,则相应字段信息会被清空,请谨慎填写
+        </p>
+        <div v-if="uploadShow">
+          <el-upload
+            class="upload-demo"
+            drag
+            multiple
+            :before-upload="file => handleUploadChange(file)"
+            :show-file-list="false"
+          >
+            <el-icon class="el-icon--upload"><upload-filled /></el-icon>
+            <div class="el-upload__text">
+              点击<em>图标选择上传</em>或者将文件拖拽到此区域
+            </div>
+          </el-upload>
+        </div>
+        <div v-else class="mb-2">
+          <el-table
+            :data="tableData"
+            border
+            style="width: 100%"
+            max-height="250"
+          >
+            <el-table-column prop="name" label="指标名称" />
+            <el-table-column prop="define" label="指标定义" />
+            <el-table-column prop="caliber" label="指标口径" />
+            <el-table-column prop="categoryName" label="指标分类" />
+            <el-table-column prop="source" label="来源" />
+            <el-table-column prop="statue" label="状态">
+              <template #default="scope">
+                {{ stateDone(scope.row.statue) }}
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+        <div class="float-right">
+          <el-button @click="backDefine">取消</el-button>
+          <el-button type="primary" @click="uploadFile">确认</el-button>
+        </div>
+        <p class="h-11 no-select">.</p>
+      </div>
+    </div>
+  </div>
+</template>
+<style lang="scss" scoped>
+.bg {
+  background-color: #f8f9fa;
+}
+
+.text {
+  color: #999;
+}
+
+.upoload {
+  float: right;
+}
+
+.no-select {
+  color: rgb(255 255 255 / 0%);
+  user-select: none;
+}
+
+.float-right {
+  margin-top: 10px;
+}
+</style>

+ 82 - 0
src/views/indexData/components/logDrawer.vue

@@ -0,0 +1,82 @@
+<!--
+ * @Author: zhanghaifeng
+ * @Date: 2025-01-03 16:46:09
+ * @LastEditors: zhanghaifeng
+ * @LastEditTime: 2025-01-03 17:10:33
+ * @Description:
+ * @FilePath: /hospital-project/src/views/indexData/components/logDrawer.vue
+-->
+<script setup lang="ts">
+import { ref } from "vue";
+import type { DrawerProps } from "element-plus";
+import { getQuotaLogInfo } from "@/api/indexDefine";
+import dayjs from "dayjs";
+const drawer = ref(false);
+const quotaId = ref();
+const direction = ref<DrawerProps["direction"]>("rtl");
+const logList = ref([]);
+const getQuotaLogInfoApi = async () => {
+  const { code, data } = await getQuotaLogInfo(quotaId.value);
+  console.log(data);
+  if (code === 200) {
+    logList.value = data;
+  }
+};
+function cancelClick() {
+  drawer.value = false;
+}
+const open = row => {
+  quotaId.value = row.id;
+  getQuotaLogInfoApi();
+  drawer.value = true;
+};
+defineExpose({
+  open
+});
+</script>
+
+<template>
+  <div>
+    <el-drawer v-model="drawer" :direction="direction">
+      <template #header>
+        <h4>指标日志</h4>
+      </template>
+      <template #default>
+        <div>
+          <div v-for="(item, index) in logList" :key="index" class="w-full">
+            <div>
+              <el-text class="font-semibold">操作用户</el-text>
+              <div
+                class="bg-gray-100 pl-3 pb-2 pt-2 rounded text-sm text-gray-500"
+              >
+                {{ item.updateUser }}
+              </div>
+            </div>
+            <div class="mt-4">
+              <el-text class="font-semibold">更新时间</el-text>
+              <div
+                class="bg-gray-100 pl-3 pb-2 pt-2 rounded text-sm text-gray-500"
+              >
+                {{ dayjs(item.updateTime).format("YYYY-MM-DD HH:mm:ss") }}
+              </div>
+            </div>
+            <div class="mt-4">
+              <el-text class="font-semibold">更新内容</el-text>
+              <div
+                class="bg-gray-100 pl-3 pr-3 pb-2 pt-2 rounded text-sm text-gray-500"
+              >
+                {{ item.updateContent }}
+              </div>
+              <el-divider />
+            </div>
+          </div>
+        </div>
+      </template>
+      <template #footer>
+        <div>
+          <el-button @click="cancelClick">关闭</el-button>
+        </div>
+      </template>
+    </el-drawer>
+  </div>
+</template>

+ 246 - 0
src/views/indexData/index.vue

@@ -0,0 +1,246 @@
+<!--
+ * @Author: zhanghaifeng
+ * @Date: 2025-01-03 13:57:58
+ * @LastEditors: zhanghaifeng
+ * @LastEditTime: 2025-01-08 11:01:19
+ * @Description: 指标数据
+ * @FilePath: /hospital-project/src/views/indexData/index.vue
+-->
+<script setup>
+defineOptions({
+  name: "IndexData"
+});
+import { ElMessage, ElMessageBox } from "element-plus";
+import { ref, reactive, onMounted } from "vue";
+import { Search } from "@element-plus/icons-vue";
+import dayjs from "dayjs";
+import IndexImport from "./components/indexImport.vue";
+import addDialog from "./components/addDialog.vue";
+import editDrawer from "./components/editDrawer.vue";
+import logDrawer from "./components/logDrawer.vue";
+import { getPageList, delListItemApi } from "@/api/indexData";
+
+// 状态
+const params = reactive({
+  params: {
+    pageNumber: 1,
+    pageSize: 10,
+    sourceValue: "",
+    deptName: "",
+    userName: ""
+  },
+  records: [],
+  total: 0
+});
+
+// 获取列表数据
+const getList = async () => {
+  const { code, data } = await getPageList(params.params);
+  if (code === 200) {
+    params.records = data.records;
+    params.total = data.totalRow;
+  }
+};
+const handleSizeChange = val => {
+  params.params.pageSize = val;
+  getList();
+};
+const handleCurrentChange = val => {
+  params.params.pageNumber = val;
+  getList();
+};
+onMounted(() => {
+  getList();
+});
+
+// 批量导入
+const sizeImport = ref(false);
+const bulkImport = () => {
+  sizeImport.value = !sizeImport.value;
+};
+
+// 添加
+const dialogShow = ref(null);
+const showAddData = () => {
+  dialogShow.value && dialogShow.value.open();
+};
+
+// 编辑/详情
+const editdrawerShow = ref(null);
+const setEdit = row => {
+  editdrawerShow.value && editdrawerShow.value.open(row);
+};
+
+// 删除
+const setDelete = row => {
+  ElMessageBox.confirm("确定要继续删除这项数据吗?", "数据删除后将无法恢复", {
+    confirmButtonText: "确认",
+    cancelButtonText: "取消",
+    type: "warning"
+  })
+    .then(async () => {
+      const { code } = await delListItemApi({
+        sourceDateId: row.id
+      });
+      if (code === 200) {
+        ElMessage({
+          type: "success",
+          message: "删除成功"
+        });
+        getList();
+      }
+    })
+    .catch(() => {
+      ElMessage({
+        type: "info",
+        message: "已取消"
+      });
+    });
+};
+
+// 日志
+const logDrawerRef = ref(null);
+const showLogs = row => {
+  logDrawerRef.value && logDrawerRef.value.open(row);
+};
+</script>
+
+<template>
+  <div class="page-container">
+    <!-- 批量导入 -->
+    <IndexImport v-if="sizeImport" @handleImport="bulkImport" />
+    <div v-else>
+      <div class="w-[100%]">
+        <div class="mb-2 flex gap-2 justify-between flex-wrap">
+          <div class="flex mt-2">
+            <div class="flex mr-2">
+              <el-input
+                v-model="params.params.sourceValue"
+                style="width: 200px"
+                :prefix-icon="Search"
+                clearable
+                placeholder="搜索指标名称"
+                @change="getList"
+              />
+            </div>
+            <!--暂时注释,产品说下一期迭代-->
+            <!-- <div class="flex mr-2">
+              <el-input v-model="params.params.deptName" style="width: 200px" :prefix-icon="Search" clearable
+                placeholder="搜索部门名称" @change="getList" />
+            </div>
+            <div class="flex mr-2">
+              <el-input v-model="params.params.userName" style="width: 200px" :prefix-icon="Search" clearable
+                placeholder="搜索员工名称" @change="getList" />
+            </div> -->
+          </div>
+          <div class="flex pt-2 mr-6">
+            <el-button class="mr-2" @click="bulkImport">批量导入</el-button>
+            <el-button type="primary" class="mr-2" @click="showAddData">
+              添加数据
+            </el-button>
+          </div>
+        </div>
+      </div>
+      <div class="mt-8">
+        <el-table :data="params.records" style="width: 100%">
+          <el-table-column
+            prop="sourceValue"
+            label="指标名称"
+            fixed
+            width="150"
+          />
+          <el-table-column prop="deptName" label="部门名称" width="200" />
+          <el-table-column prop="userName" label="员工名称" />
+          <el-table-column prop="userId" label="工号" width="120" />
+          <el-table-column prop="numerator" label="分子" />
+          <el-table-column prop="denominator" label="分母" />
+          <el-table-column prop="dataValue" label="值" />
+          <el-table-column prop="specificTime" label="数据时间" width="120" />
+          <el-table-column prop="updateTime" label="更新时间" width="200">
+            <template #default="{ row }">
+              {{
+                row.updateTime
+                  ? dayjs(row.updateTime).format("YYYY-MM-DD HH:mm:ss")
+                  : "无效日期"
+              }}
+            </template>
+          </el-table-column>
+          <el-table-column prop="createUser" label="创建人" />
+          <el-table-column label="操作" fixed="right" width="120">
+            <template #default="{ row }">
+              <el-dropdown trigger="click">
+                <span class="el-dropdown-link navbar-bg-hover select-none pt-1">
+                  <el-icon @click="setEdit(row)">
+                    <Edit />
+                  </el-icon>
+                </span>
+              </el-dropdown>
+              <el-dropdown class="ml-2" trigger="click">
+                <span class="el-dropdown-link navbar-bg-hover select-none pt-1">
+                  <el-icon>
+                    <More />
+                  </el-icon>
+                </span>
+                <template #dropdown>
+                  <el-dropdown-menu class="setting">
+                    <el-dropdown-item @click="setDelete(row)">
+                      <el-text type="danger">删除</el-text>
+                    </el-dropdown-item>
+                    <el-dropdown-item @click="showLogs(row)"
+                      >日志</el-dropdown-item
+                    >
+                  </el-dropdown-menu>
+                </template>
+              </el-dropdown>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+      <div class="flex justify-between item-center">
+        <div class="float-left mt-5 ml-2 total">共{{ params.total }}条数据</div>
+        <div class="float-right mt-5 mr-8">
+          <el-pagination
+            v-model:current-page="params.params.pageNumber"
+            v-model:page-size="params.params.pageSize"
+            background
+            layout="prev, pager, next"
+            :total="params.total"
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+          />
+        </div>
+      </div>
+      <!-- 编辑/详情 -->
+      <editDrawer ref="editdrawerShow" @updateDialog="getList" />
+      <!-- 新建 -->
+      <addDialog ref="dialogShow" @updateDialog="getList" />
+      <!-- 日志 -->
+      <logDrawer ref="logDrawerRef" />
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.total {
+  font-size: 14px;
+  font-weight: 400;
+  line-height: 22px;
+  color: #0009;
+  letter-spacing: 0;
+}
+
+.header {
+  display: flex;
+  width: 100%;
+  line-height: 100%;
+  border: 1px solid red;
+}
+
+::v-deep(.el-table .el-table__header th) {
+  background-color: #f2f3f5;
+}
+
+.setting {
+  font-size: 12px;
+}
+</style>