Parcourir la source

fix: 排行榜导出

ystl_myq il y a 2 mois
Parent
commit
283100c433

+ 1 - 1
.vscode/settings.json

@@ -2,7 +2,7 @@
   "editor.formatOnType": true,
   "editor.formatOnSave": true,
   "[vue]": {
-    "editor.defaultFormatter": "vscode.typescript-language-features"
+    "editor.defaultFormatter": "Vue.volar"
   },
   "editor.tabSize": 2,
   "editor.formatOnPaste": true,

+ 3 - 1
package.json

@@ -63,6 +63,7 @@
     "default-passive-events": "^2.0.0",
     "echarts": "^5.5.0",
     "element-plus": "^2.6.2",
+    "exceljs": "^4.4.0",
     "js-cookie": "^3.0.5",
     "jsencrypt": "^3.3.2",
     "localforage": "^1.10.0",
@@ -79,7 +80,8 @@
     "vue-router": "^4.3.0",
     "vue-tippy": "^6.4.1",
     "vue-types": "^5.1.1",
-    "vxe-table": "^4.6.23",
+    "vxe-table": "^4.6.25",
+    "vxe-table-plugin-export-xlsx": "^4.0.7",
     "xlsx": "^0.18.5"
   },
   "devDependencies": {

Fichier diff supprimé car celui-ci est trop grand
+ 455 - 4
pnpm-lock.yaml


+ 37 - 44
src/components/rankTable/draw.vue

@@ -122,11 +122,16 @@ defineExpose({
 });
 </script> -->
 <template>
-  <div class="w-full">
+  <div class="w-full aa">
+    <vxe-button class="float-right -top-0 mr-20 mb-4" @click="exportEvent"
+      ><el-icon><Download /></el-icon>导出</vxe-button
+    >
     <vxe-grid
       v-if="$route.name != 'workerDrak'"
       v-bind="gridOptions"
+      ref="gridRef"
       :merge-cells="mergeCells"
+      class="mt-10"
       v-on="gridEvents"
     >
       <template #imgUrl_default="{ row }">
@@ -147,7 +152,7 @@ defineExpose({
         {{ row.allScore }}
       </template>
     </vxe-grid>
-    <vxe-grid v-else v-bind="gridOptions">
+    <vxe-grid v-else v-bind="gridOptions" ref="gridRef1" class="mt-10">
       <template #imgUrl_default="{ row }">
         <div v-if="row.ranking <= 3" class="text-center">
           <img class="mt-auto" :src="RANK_IMG[Number(row.ranking) - 1]" />
@@ -166,11 +171,14 @@ import { ref, reactive, watch, onMounted } from "vue";
 import rank1 from "@/assets/rank/rank1.png";
 import rank2 from "@/assets/rank/rank2.png";
 import rank3 from "@/assets/rank/rank3.png";
+import dayjs from "dayjs";
 import { useRoute } from "vue-router";
 import { useUserStoreHook } from "@/store/modules//user";
 const $prop = defineProps({
   rankTableRefList: Object
 });
+const gridRef = ref(null);
+const gridRef1 = ref(null);
 onMounted(() => {
   // 员工排行榜无法获取组件渲染问题
   if ($prop.rankTableRefList) {
@@ -331,48 +339,6 @@ const createColumns = () => {
   }
   const headerMap = new Map();
   // 遍历数据生成表头结构
-  // rawData.value.forEach(item => {
-  //   item.dimensionList.forEach(dim => {
-  //     if (!headerMap.has(dim.dimName)) {
-  //       // 存储维度相关信息,包括 totalSore
-  //       console.log("dim", dim);
-  //       headerMap.set(dim.dimName, {
-  //         totalSore: dim.totalSore,
-  //         quotas: [] // 用于存储 quota 列表
-  //       });
-  //     }
-  //     dim.quotaList.forEach(quota => {
-  //       const dimData = headerMap.get(dim.dimName);
-  //       if (!dimData.quotas.some(q => q.quotaName === quota.quotaName)) {
-  //         dimData.quotas.push({ quotaName: quota.quotaName });
-  //       }
-  //     });
-  //   });
-  // });
-
-  // // 构建多级表头
-  // headerMap.forEach((dimData, dimName) => {
-  //   const children = dimData.quotas.map(quota => ({
-  //     title: quota.quotaName,
-  //     width: 120,
-  //     sortable: true,
-  //     field: `${dimName}_${quota.quotaName}` // 确保字段唯一
-  //   }));
-
-  //   // 添加维度名称及其对应的子列
-  //   columns.push({
-  //     title: dimName,
-  //     children
-  //   });
-
-  //   // 添加该维度的总分列
-  //   columns.push({
-  //     title: `总分`, // 可根据需要调整标题
-  //     field: `${dimName}_totalSore`,
-  //     width: 120,
-  //     sortable: true
-  //   });
-  // });
   rawData.value.forEach(item => {
     item.dimensionList.forEach(dim => {
       if (!headerMap.has(dim.dimName)) {
@@ -465,6 +431,29 @@ watch(selectYear, () => {
   createColumns();
   loadData();
 });
+// 导出
+const exportEvent = () => {
+  let $grid;
+  let fileName =
+    localStorage.getItem("fileName") + dayjs().format("YYYY-MM-DD HH:mm:ss");
+  if ($route.name != "workerDrak") {
+    $grid = gridRef.value;
+  } else {
+    $grid = gridRef1.value;
+  }
+  if ($grid) {
+    $grid.exportData({
+      type: "xlsx",
+      filename: fileName,
+      useStyle: true
+    });
+  } else {
+    ElMessage({
+      message: "暂无数据",
+      type: "warning"
+    });
+  }
+};
 </script>
 
 <style lang="scss" scoped>
@@ -490,4 +479,8 @@ watch(selectYear, () => {
 
   /* 背景颜色 */
 }
+
+.aa {
+  position: relative;
+}
 </style>

+ 129 - 31
src/components/rankTable/index.vue

@@ -134,13 +134,17 @@ defineExpose({
 </script> -->
 
 <template>
-  <div class="w-full">
+  <div class="w-full aa">
+    <vxe-button class="float-right -top-10 mr-20 mb-4" @click="exportEvent"
+      ><el-icon> <Download /> </el-icon>导出</vxe-button
+    >
     <vxe-grid
       v-if="$route.name != 'workerDrak'"
       v-bind="gridOptions"
       ref="gridRef"
+      class="mt-10"
       :merge-cells="mergeCells"
-      v-on="gridEvents"
+      @sortChange="changeSort"
     >
       <template #imgUrl_default="{ row }">
         <div>
@@ -160,7 +164,13 @@ defineExpose({
         {{ row.allScore }}
       </template>
     </vxe-grid>
-    <vxe-grid v-else v-bind="gridOptions">
+    <vxe-grid
+      v-else
+      v-bind="gridOptions"
+      ref="gridRef1"
+      class="mt-10"
+      @sortChange="changeSort"
+    >
       <template #imgUrl_default="{ row }">
         <div v-if="row.ranking <= 3" class="text-center">
           <img class="mt-auto" :src="RANK_IMG[Number(row.ranking) - 1]" />
@@ -175,12 +185,15 @@ defineExpose({
 </template>
 
 <script setup>
+import { ElMessage } from "element-plus";
 import { ref, reactive, watch, onMounted, nextTick } from "vue";
 import rank1 from "@/assets/rank/rank1.png";
 import rank2 from "@/assets/rank/rank2.png";
 import rank3 from "@/assets/rank/rank3.png";
+import dayjs from "dayjs";
 import { useRoute } from "vue-router";
 const gridRef = ref(null);
+const gridRef1 = ref(null);
 const $route = useRoute();
 const RANK_IMG = [rank1, rank2, rank3];
 const deptName = ref("");
@@ -199,7 +212,8 @@ const averageData = averageSore => {
     // num = num / rawData.value.length;
     rawData.value.unshift({
       dimensionList: [],
-      allScore: rawData.value[0].averageSore
+      allScore: rawData.value[0].averageSore,
+      isAverageRow: true // 标记为平均分行
     });
   }
 };
@@ -209,8 +223,9 @@ const gridOptions = reactive({
   loading: false,
   height: 500,
   sortConfig: {
-    multiple: true
+    multiple: false
   },
+  exportConfig: {},
   columns: [],
   data: [],
   showOverflow: true,
@@ -250,27 +265,79 @@ defineExpose({
 // 排序状态
 const gridEvents = {
   sortChange({ column, order }) {
-    // 如果没有选择排序列或没有排序方向,则直接返回
     if (!column || !order) {
-      gridOptions.data = [...originalData.value];
+      gridOptions.data = [...originalData.value]; // 恢复到原始数据
     } else {
-      // 排除第一行,如果路由名称不是 "workerDrak"
-      const rowsToSort = gridOptions.data.slice(1); // 排除第一行
-      const sortedRows = rowsToSort.sort((a, b) => {
-        // 根据点击的列字段和排序方向进行排序
-        if (order === "asc") {
-          return a[column.field] < b[column.field] ? -1 : 1;
-        } else {
-          return a[column.field] > b[column.field] ? -1 : 1;
+      const sortedData = [...originalData.value];
+      // 先将所有 isAverageRow 为 true 的行提取出来
+      const averageRows = sortedData.filter(item => item.isAverageRow);
+      const otherRows = sortedData.filter(item => !item.isAverageRow);
+
+      // 对其他数据进行排序
+      otherRows.sort((a, b) => {
+        const field = column.field;
+        const aValue = a[field];
+        const bValue = b[field];
+
+        // 根据正序倒序来决定排序规则
+        if (order === "ascending") {
+          return aValue > bValue ? 1 : aValue < bValue ? -1 : 0;
+        } else if (order === "descending") {
+          return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
         }
+        return 0;
       });
 
-      // 将第一行重新插入到排序后的数据顶部
-      const sortedData = [gridOptions.data[0], ...sortedRows];
-      gridOptions.data = sortedData; // 更新表格数据
+      // 将排序后的数据与平均分行数据合并,平均分行始终放在最前面
+      gridOptions.data = [...averageRows, ...otherRows];
+
+      // 使用深拷贝确保数据变化被检测到
+      nextTick(() => {
+        if (gridRef.value) {
+          // 使用 loadData 传递深拷贝的数据
+          gridRef.value.loadData([...averageRows, ...otherRows]);
+        }
+      });
     }
   }
 };
+function changeSort({ column, order }) {
+  if (!column || !order) {
+    gridOptions.data = [...originalData.value]; // 恢复到原始数据
+  } else {
+    const sortedData = [...originalData.value];
+    // 先将所有 isAverageRow 为 true 的行提取出来
+    const averageRows = sortedData.filter(item => item.isAverageRow);
+    const otherRows = sortedData.filter(item => !item.isAverageRow);
+
+    // 对其他数据进行排序
+    otherRows.sort((a, b) => {
+      const field = column.field;
+      const aValue = a[field];
+      const bValue = b[field];
+
+      // 根据正序倒序来决定排序规则
+      if (order === "ascending") {
+        return aValue > bValue ? 1 : aValue < bValue ? -1 : 0;
+      } else if (order === "descending") {
+        return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
+      }
+      return 0;
+    });
+
+    // 将排序后的数据与平均分行数据合并,平均分行始终放在最前面
+    gridOptions.data = [...averageRows, ...otherRows];
+    console.log("132131排序", [...averageRows, ...otherRows]);
+    // 使用深拷贝确保数据变化被检测到
+    nextTick(() => {
+      if (gridRef.value) {
+        // 使用 loadData 传递深拷贝的数据
+        gridRef.value.loadData([...averageRows, ...otherRows]);
+      }
+    });
+  }
+}
+
 // 生成动态表头
 const createColumns = () => {
   let columns = [];
@@ -392,7 +459,8 @@ const loadData = () => {
       assessmentObjectName: item.assessmentObjectName,
       deptName: item.deptName,
       ranking: item.ranking,
-      allScore: item.allScore
+      allScore: item.allScore,
+      isAverageRow: item.isAverageRow || false // 确保数据里标记了是否为平均分行
     };
 
     item.dimensionList.forEach(dim => {
@@ -407,33 +475,28 @@ const loadData = () => {
 
       // 填充维度总分
       const totalFieldName = `${dim.dimName}_totalSore`;
-      row[totalFieldName] = dim.totalSore || totalScore; // 使用 dim.totalSore 或计算得出的总分
+      row[totalFieldName] = dim.totalSore || totalScore;
     });
 
     return row;
   });
-  originalData.value = [...tableData]; // 深拷贝原始数据
+
+  originalData.value = [...tableData]; // 保存原始数据
+  tableData[0].sortable = true;
   gridOptions.data = tableData;
 
-  // 使用 nextTick 确保表格渲染完成后再合并单元格
+  // 使用 nextTick 确保表格渲染完成后再合并单元格
   nextTick(() => {
     if (gridRef.value) {
       gridRef.value.setMergeCells([
-        // 合并第一行前两列
         { row: 0, col: 0, rowspan: 1, colspan: 2 },
         { row: 2, col: 1, rowspan: 0, colspan: 0 }
-      ]); // 重新合并单元格
+      ]);
     }
   });
-  // setTimeout(() => {
-  //   // 重新应用合并单元格
-  //   if (gridRef.value) {
-  //     gridRef.value.mergeCells(); // 重新合并单元格
-  //   }
-  // }, 500);
+
   gridOptions.loading = false;
 };
-
 // 监听年份变化,重新渲染
 watch(selectYear, () => {
   createColumns();
@@ -455,6 +518,37 @@ onMounted(() => {
   // 监听窗口大小变化,动态调整表格高度
   window.addEventListener("resize", setGridHeight);
 });
+// 导出
+const exportEvent = () => {
+  let $grid;
+  let fileName =
+    localStorage.getItem("fileName") + dayjs().format("YYYY-MM-DD HH:mm:ss");
+  if ($route.name != "workerDrak") {
+    $grid = gridRef.value;
+  } else {
+    $grid = gridRef1.value;
+  }
+  if ($grid) {
+    // gridOptions.exportConfig = {
+    //   type: "xlsx",
+    //   fileName: name
+    // };
+    $grid.exportData({
+      type: "xlsx",
+      filename: fileName,
+      useStyle: true
+    });
+    // $grid.exportData({
+    //   type: "xlsx",
+    //   fileName: name
+    // });
+  } else {
+    ElMessage({
+      message: "暂无数据",
+      type: "warning"
+    });
+  }
+};
 </script>
 <style lang="scss" scoped>
 .diamond {
@@ -490,4 +584,8 @@ onMounted(() => {
 .aaaa {
   background-color: black !important;
 }
+
+.aa {
+  position: relative;
+}
 </style>

+ 5 - 0
src/main.ts

@@ -35,6 +35,11 @@ Object.keys(directives).forEach(key => {
 });
 import VxeUITable from "vxe-table";
 import "vxe-table/lib/style.css";
+import VXETablePluginExportXLSX from "vxe-table-plugin-export-xlsx";
+import ExcelJS from "exceljs";
+VxeUITable.use(VXETablePluginExportXLSX, {
+  ExcelJS
+});
 // 全局注册@iconify/vue图标库
 import {
   IconifyIconOffline,

+ 1 - 0
src/views/draw/children/department/componements/seach.vue

@@ -89,6 +89,7 @@ const handClickInit2 = value => {
       item => item.assessmentModelId === value
     );
     init.assessmentModelName = selectedItem.assessmentModelName;
+    localStorage.setItem("fileName", selectedItem.assessmentModelName);
     userPageWhitOrganizationApi(selectedItem, 0);
     postListTreeApi(selectedItem);
   }

+ 1 - 0
src/views/draw/children/head/componements/seach.vue

@@ -106,6 +106,7 @@ const handClickInit2 = value => {
       item => item.assessmentModelId === value
     );
     init.assessmentModelName = selectedItem.assessmentModelName;
+    localStorage.setItem("fileName", selectedItem.assessmentModelName);
     getLeaderListApi(selectedItem);
   }
   Object.assign(dataList.params, {

+ 1 - 0
src/views/draw/children/health/componements/seach.vue

@@ -89,6 +89,7 @@ const handClickInit2 = value => {
       item => item.assessmentModelId === value
     );
     init.assessmentModelName = selectedItem.assessmentModelName;
+    localStorage.setItem("fileName", selectedItem.assessmentModelName);
     postPageGroupApi(selectedItem);
   }
   Object.assign(dataList.params, {

+ 1 - 0
src/views/draw/children/worker/componements/seach.vue

@@ -123,6 +123,7 @@ const handClickInit2 = value => {
       item => item.assessmentModelId === value
     );
     init.assessmentModelName = selectedItem.assessmentModelName;
+    localStorage.setItem("fileName", selectedItem.assessmentModelName);
     userPageWhitOrganizationApi(selectedItem, 0);
     // postListTreeApi(selectedItem);
   }

+ 0 - 1
src/views/draw/children/worker/workerDrak.vue

@@ -150,7 +150,6 @@ const init = (item, type) => {
     // getPersonDimensionChartsRankingApi();
     getPersonDimensionChartsListApiDimList();
     getChartsListApi();
-    // debugger;
     activeName.value = "1";
   }
   setTimeout(() => {

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

@@ -335,7 +335,6 @@ const onStart = (e: DraggableEvent) => {};
 const onUpdate = (e: DraggableEvent) => {};
 
 const postUpdateApi = async row => {
-  console.log();
   // indexOf.indName = indexOf.name
   const { code, msg } = await postUpdate(row);
   if (code == 200) {

+ 0 - 1
src/views/login/index.vue

@@ -71,7 +71,6 @@ const onLogin = async (formEl: FormInstance | undefined) => {
             // @ts-ignore
             localStorage.setItem("userName", res.data.realName);
             //  获取后端路由动态路由逻辑,不可删除
-            // debugger;
             initRouter().then(() => {
               // router.push(getTopMenu(true).path);
               router.push("/");

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff