Browse Source

feat: 排行榜导出排序

ystl_myq 5 months ago
parent
commit
2d16a9da74
5 changed files with 232 additions and 254 deletions
  1. 1 0
      package.json
  2. 7 4
      pnpm-lock.yaml
  3. 117 29
      src/components/rankTable/draw.vue
  4. 106 221
      src/components/rankTable/index.vue
  5. 1 0
      src/main.ts

+ 1 - 0
package.json

@@ -82,6 +82,7 @@
     "vue-types": "^5.1.1",
     "vxe-table": "^4.6.25",
     "vxe-table-plugin-export-xlsx": "^4.0.7",
+    "xe-utils": "^3.5.32",
     "xlsx": "^0.18.5"
   },
   "devDependencies": {

+ 7 - 4
pnpm-lock.yaml

@@ -104,6 +104,9 @@ importers:
       vxe-table-plugin-export-xlsx:
         specifier: ^4.0.7
         version: 4.0.7(vxe-table@4.6.25(vue@3.4.21(typescript@5.4.3)))
+      xe-utils:
+        specifier: ^3.5.32
+        version: 3.5.32
       xlsx:
         specifier: ^0.18.5
         version: 0.18.5
@@ -4052,8 +4055,8 @@ packages:
     resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
 
-  xe-utils@3.5.31:
-    resolution: {integrity: sha512-oS4yv8qktvlE0wc9yYkitDidEmThc5qN0UTRvKCvrWnejxbTyIxbwfrdZmPKdKGZtB+/U8cEAMFywLJjHtD11A==}
+  xe-utils@3.5.32:
+    resolution: {integrity: sha512-R8ZT2lRnRBQO3pchM1za/Aru+/29DVDWD/OmOFODWWGkiQYz0iVIr8Bq8uKXS6zMhEsSqVCrn46bXzfe/Agjcw==}
 
   xlsx@0.18.5:
     resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
@@ -8050,7 +8053,7 @@ snapshots:
     dependencies:
       dom-zindex: 1.0.6
       vue: 3.4.21(typescript@5.4.3)
-      xe-utils: 3.5.31
+      xe-utils: 3.5.32
 
   webpack-sources@3.2.3:
     optional: true
@@ -8099,7 +8102,7 @@ snapshots:
       imurmurhash: 0.1.4
       signal-exit: 4.1.0
 
-  xe-utils@3.5.31: {}
+  xe-utils@3.5.32: {}
 
   xlsx@0.18.5:
     dependencies:

+ 117 - 29
src/components/rankTable/draw.vue

@@ -132,7 +132,6 @@ defineExpose({
       ref="gridRef"
       :merge-cells="mergeCells"
       class="mt-10"
-      v-on="gridEvents"
     >
       <template #imgUrl_default="{ row }">
         <div>
@@ -208,7 +207,8 @@ const averageData = averageSore => {
     // num = (num / rawData.value.length).toFixed(2);
     rawData.value.unshift({
       dimensionList: [],
-      allScore: rawData.value[0].averageSore
+      allScore: rawData.value[0].averageSore,
+      isAverageRow: true // 标记为平均分行
     });
   }
 };
@@ -219,6 +219,10 @@ const gridOptions = reactive({
   height: 500,
   columns: [],
   data: [],
+  sortConfig: {
+    multiple: false,
+    chronological: true
+  },
   showOverflow: true,
   showHeaderOverflow: true,
   showFooterOverflow: true,
@@ -230,30 +234,14 @@ const gridOptions = reactive({
     gt: 0
   }
 });
-// 排序状态
-const gridEvents = {
-  sortChange({ column, order }) {
-    // 如果没有选择排序列或没有排序方向,则直接返回
-    if (!column || !order) {
-      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 = [gridOptions.data[0], ...sortedRows];
-      gridOptions.data = sortedData; // 更新表格数据
-    }
+// 判断key值是否相同返回具体值
+function getValueByKey(obj, key) {
+  if (key in obj) {
+    return obj[key];
   }
-};
+  return null; // 如果 key 不存在则返回 null
+}
 // 模拟接口数据
 const rawData = ref([]);
 const init = (item, deptNames, names) => {
@@ -309,7 +297,24 @@ const createColumns = () => {
         sortable: true,
         width: 150,
         fixed: "right", // 将此列固定在右侧
-        slots: { default: "dim-allScore" }
+        slots: { default: "dim-allScore" },
+        sortBy({ column, row }) {
+          // 如果是平均行(isAverageRow为true),则返回一个很大的值,避免被排序
+          if (column.field in row && typeof row[column.field] === "number") {
+            if (column.order == "asc") {
+              if (row.isAverageRow) {
+                return -9999999999;
+              }
+            } else {
+              if (row.isAverageRow) {
+                return 99999999999;
+              }
+            }
+          } else {
+            return -9999999999;
+          }
+          return row.allScore; // 返回正常的得分,进行排序
+        }
       }
     ];
   } else {
@@ -333,7 +338,27 @@ const createColumns = () => {
         width: 150,
         sortable: true,
         fixed: "right", // 将此列固定在右侧
-        slots: { default: "dim-allScore" }
+        slots: { default: "dim-allScore" },
+        sortBy({ column, row }) {
+          // 如果是平均行(isAverageRow为true),则返回一个很大的值,避免被排序
+
+          // console.log(123, column)
+          // console.log("1231row", row)
+          if (column.field in row && typeof row[column.field] === "number") {
+            if (column.order == "asc") {
+              if (row.isAverageRow) {
+                return -9999999999;
+              }
+            } else {
+              if (row.isAverageRow) {
+                return 99999999999;
+              }
+            }
+          } else {
+            return -9999999999;
+          }
+          return getValueByKey(row, column.field); // 返回正常的得分,进行排序
+        }
       }
     ];
   }
@@ -367,7 +392,38 @@ const createColumns = () => {
       title: `${quota.quotaName} (${quota.quotaWeight}%)`, // 在 title 后拼接 quotaWeight
       width: 120,
       sortable: true,
-      field: `${dimName}_${quota.quotaName}` // 确保字段唯一
+      field: `${dimName}_${quota.quotaName}`, // 确保字段唯一
+      sortBy({ column, row }) {
+        // 如果是平均行(isAverageRow为true),则返回一个很大的值,避免被排序
+        let aa = Number(row[column.field]);
+        if (typeof aa === "number" && !isNaN(aa)) {
+          if (column.order == "asc") {
+            if (row.isAverageRow) {
+              return -9999999999;
+            }
+          } else {
+            if (row.isAverageRow) {
+              return 99999999999;
+            }
+          }
+        } else {
+          // 倒序在最后
+          if (column.order == "asc") {
+            if (!row.isAverageRow) {
+              return 9999999999999;
+            } else {
+              return -999999999999;
+            }
+          } else {
+            if (!row.isAverageRow) {
+              return -999999999999;
+            } else {
+              return 999999999999;
+            }
+          }
+        }
+        return getValueByKey(row, column.field); // 返回正常的得分,进行排序
+      }
     }));
 
     // 添加维度名称及其对应的子列
@@ -381,7 +437,38 @@ const createColumns = () => {
       title: `总分`, // 可根据需要调整标题
       field: `${dimName}_totalSore`,
       width: 120,
-      sortable: true
+      sortable: true,
+      sortBy({ column, row }) {
+        // 如果是平均行(isAverageRow为true),则返回一个很大的值,避免被排序
+        let aa = Number(row[column.field]);
+        if (typeof aa === "number" && !isNaN(aa)) {
+          if (column.order == "asc") {
+            if (row.isAverageRow) {
+              return -9999999999;
+            }
+          } else {
+            if (row.isAverageRow) {
+              return 99999999999;
+            }
+          }
+        } else {
+          // 倒序在最后
+          if (column.order == "asc") {
+            if (!row.isAverageRow) {
+              return 9999999999999;
+            } else {
+              return -999999999999;
+            }
+          } else {
+            if (!row.isAverageRow) {
+              return -999999999999;
+            } else {
+              return 999999999999;
+            }
+          }
+        }
+        return getValueByKey(row, column.field); // 返回正常的得分,进行排序
+      }
     });
   });
   gridOptions.columns = columns;
@@ -398,7 +485,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 => {

+ 106 - 221
src/components/rankTable/index.vue

@@ -1,138 +1,3 @@
-<!-- <template>
-  <vxe-table
-    border
-    style="width: 100%"
-    :data="tableData"
-    show-overflow
-    show-header-overflow
-    show-footer-overflow
-    :column-config="{ resizable: true }"
-    :scroll-x="{ enabled: true, gt: 0 }"
-    :merge-cells="mergeCells"
-  >
-    <vxe-column field="index" title="排名" width="80" fixed="left">
-      <template #default="scope">
-        <div
-          v-if="scope._rowIndex == 0"
-          class="text-center text-sm font-extrabold text-[#000000] pt-4"
-        >
-          平均得分
-        </div>
-        <div v-if="scope._rowIndex > 3" class="diamond">
-          {{ scope.row.ranking }}
-        </div>
-        <div v-else class="text-center">
-          <img :src="RANK_IMG[scope.row.ranking - 1]" alt="" />
-        </div>
-      </template>
-    </vxe-column>
-    <vxe-column
-      v-if="$route.name == 'workerDrak' || $route.name == 'workerRank'"
-      field="deptName"
-      :title="tabTitle"
-      fixed="left"
-      width="150"
-    />
-    <vxe-column
-      v-else
-      field="assessmentObjectName"
-      :title="deptName"
-      fixed="left"
-      width="150"
-    />
-    <vxe-column
-      v-if="$route.name == 'workerDrak' || $route.name == 'workerRank'"
-      field="assessmentObjectName"
-      fixed="left"
-      title="姓名"
-      width="100"
-    />
-    <template v-for="(ita, itk) in tableData" :key="itk">
-      <template v-for="item in ita.dimensionList" :key="item.dimId">
-        <vxe-colgroup
-          :title="`${item.dimName}(${soreRate(item.soreRate)}%)`"
-          width="150"
-        >
-          <template v-for="it in item.quotaList" :key="it.quotaId">
-            <vxe-column
-              :field="it.quotaScore"
-              :title="`${it.quotaName}(${soreRate(it.quotaWeight)}%)`"
-              width="150"
-            />
-          </template>
-        </vxe-colgroup>
-        <vxe-column field="totalSore" title="总分" width="150" />
-      </template>
-    </template>
-    <vxe-column field="allScore" title="总得分" fixed="right" width="150" />
-  </vxe-table>
-</template>
-
-<script lang="ts" setup>
-import { computed, reactive, ref, 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 { useRoute } from "vue-router";
-const $route = useRoute();
-const RANK_IMG = [rank1, rank2, rank3];
-const deptName = ref("");
-const name = ref("");
-const tableData = ref();
-const tabTitle = ref("");
-const mergeCells = ref([
-  // 合并第一行前两列
-  { row: 0, col: 0, rowspan: 1, colspan: 2 },
-  { row: 2, col: 1, rowspan: 0, colspan: 0 }
-]);
-onMounted(() => {
-  cellTable();
-});
-const cellTable = () => {
-  // if ($route.name == "workerRank") {
-  //   mergeCells.value = [
-  //     { row: 0, col: 0, rowspan: 1, colspan: 3 },
-  //     { row: 2, col: 1, rowspan: 1, colspan: 3 }
-  //   ];
-  // }
-  if ($route.name == "healthDrank" || $route.name == "healthRank") {
-    tabTitle.value = "医疗组";
-  } else {
-    tabTitle.value = "科室";
-  }
-};
-const averageData = averageSore => {
-  if ($route.name != "workerDrak" || $route.name != "workerRank") {
-    if (tableData.value.length > 0) {
-      tableData.value.unshift({
-        dimensionList: [],
-        allScore: averageSore
-      });
-    }
-  }
-};
-const init = (item, deptNames, names) => {
-  tableData.value = item;
-  deptName.value = deptNames;
-  name.value = names;
-  if ($route.name != "workerDrak") {
-    averageData(item.averageSore);
-  }
-  console.log("tableData.value", tableData.value);
-  console.log("deptName.value", item);
-};
-const soreRate = row => {
-  if (row) {
-    return row;
-  } else {
-    return 0;
-  }
-};
-defineExpose({
-  init
-});
-</script> -->
-
 <template>
   <div class="w-full aa">
     <vxe-button class="float-right -top-10 mr-20 mb-4" @click="exportEvent"
@@ -144,7 +9,6 @@ defineExpose({
       ref="gridRef"
       class="mt-10"
       :merge-cells="mergeCells"
-      @sortChange="changeSort"
     >
       <template #imgUrl_default="{ row }">
         <div>
@@ -164,13 +28,7 @@ defineExpose({
         {{ row.allScore }}
       </template>
     </vxe-grid>
-    <vxe-grid
-      v-else
-      v-bind="gridOptions"
-      ref="gridRef1"
-      class="mt-10"
-      @sortChange="changeSort"
-    >
+    <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]" />
@@ -261,81 +119,12 @@ const init = (item, deptNames, names) => {
 defineExpose({
   init
 });
-
-// 排序状态
-const gridEvents = {
-  sortChange({ 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];
-
-      // 使用深拷贝确保数据变化被检测到
-      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]);
-      }
-    });
+// 判断key值是否相同返回具体值
+function getValueByKey(obj, key) {
+  if (key in obj) {
+    return obj[key];
   }
+  return null; // 如果 key 不存在则返回 null
 }
 
 // 生成动态表头
@@ -368,7 +157,24 @@ const createColumns = () => {
         sortable: true,
         width: 150,
         fixed: "right", // 将此列固定在右侧
-        slots: { default: "dim-allScore" }
+        slots: { default: "dim-allScore" },
+        sortBy({ column, row }) {
+          // 如果是平均行(isAverageRow为true),则返回一个很大的值,避免被排序
+          if (column.field in row && typeof row[column.field] === "number") {
+            if (column.order == "asc") {
+              if (row.isAverageRow) {
+                return -9999999999;
+              }
+            } else {
+              if (row.isAverageRow) {
+                return 99999999999;
+              }
+            }
+          } else {
+            return -9999999999;
+          }
+          return row.allScore; // 返回正常的得分,进行排序
+        }
       }
     ];
   } else {
@@ -392,7 +198,24 @@ const createColumns = () => {
         width: 150,
         sortable: true,
         fixed: "right", // 将此列固定在右侧
-        slots: { default: "dim-allScore" }
+        slots: { default: "dim-allScore" },
+        sortBy({ column, row }) {
+          // 如果是平均行(isAverageRow为true),则返回一个很大的值,避免被排序
+          if (column.field in row && typeof row[column.field] === "number") {
+            if (column.order == "asc") {
+              if (row.isAverageRow) {
+                return -9999999999;
+              }
+            } else {
+              if (row.isAverageRow) {
+                return 99999999999;
+              }
+            }
+          } else {
+            return -9999999999;
+          }
+          return row.allScore; // 返回正常的得分,进行排序
+        }
       }
     ];
   }
@@ -428,7 +251,38 @@ const createColumns = () => {
       title: `${quota.quotaName} (${quota.quotaWeight}%)`, // 在 title 后拼接 quotaWeight
       width: 120,
       sortable: true,
-      field: `${dimName}_${quota.quotaName}` // 确保字段唯一
+      field: `${dimName}_${quota.quotaName}`, // 确保字段唯一
+      sortBy({ column, row }) {
+        // 如果是平均行(isAverageRow为true),则返回一个很大的值,避免被排序
+        let aa = Number(row[column.field]);
+        if (typeof aa === "number" && !isNaN(aa)) {
+          if (column.order == "asc") {
+            if (row.isAverageRow) {
+              return -9999999999;
+            }
+          } else {
+            if (row.isAverageRow) {
+              return 99999999999;
+            }
+          }
+        } else {
+          // 倒序在最后
+          if (column.order == "asc") {
+            if (!row.isAverageRow) {
+              return 9999999999999;
+            } else {
+              return -999999999999;
+            }
+          } else {
+            if (!row.isAverageRow) {
+              return -999999999999;
+            } else {
+              return 999999999999;
+            }
+          }
+        }
+        return getValueByKey(row, column.field); // 返回正常的得分,进行排序
+      }
     }));
 
     // 添加维度名称及其对应的子列
@@ -442,7 +296,38 @@ const createColumns = () => {
       title: `总分`, // 可根据需要调整标题
       field: `${dimName}_totalSore`,
       width: 120,
-      sortable: true
+      sortable: true,
+      sortBy({ column, row }) {
+        // 如果是平均行(isAverageRow为true),则返回一个很大的值,避免被排序
+        let aa = Number(row[column.field]);
+        if (typeof aa === "number" && !isNaN(aa)) {
+          if (column.order == "asc") {
+            if (row.isAverageRow) {
+              return -9999999999;
+            }
+          } else {
+            if (row.isAverageRow) {
+              return 99999999999;
+            }
+          }
+        } else {
+          // 倒序在最后
+          if (column.order == "asc") {
+            if (!row.isAverageRow) {
+              return 9999999999999;
+            } else {
+              return -999999999999;
+            }
+          } else {
+            if (!row.isAverageRow) {
+              return -999999999999;
+            } else {
+              return 999999999999;
+            }
+          }
+        }
+        return getValueByKey(row, column.field); // 返回正常的得分,进行排序
+      }
     });
   });
   gridOptions.columns = columns;

+ 1 - 0
src/main.ts

@@ -35,6 +35,7 @@ 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, {