index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. <template>
  2. <div class="w-full aa">
  3. <vxe-button class="float-right -top-20 mr-20 mb-4 z-50" @click="exportEvent"
  4. ><el-icon> <Download /> </el-icon>&nbsp;&nbsp;导出</vxe-button
  5. >
  6. <vxe-grid
  7. v-if="$route.name != 'workerDrak'"
  8. v-bind="gridOptions"
  9. ref="gridRef"
  10. class="mt-10"
  11. :merge-cells="mergeCells"
  12. >
  13. <template #imgUrl_default="{ row }">
  14. <div>
  15. <div
  16. v-if="!row.ranking"
  17. class="text-center text-sm font-extrabold text-[#000000]"
  18. >
  19. 平均得分
  20. </div>
  21. <div v-if="row.ranking <= 3" class="text-center">
  22. <img class="mt-auto" :src="RANK_IMG[Number(row.ranking) - 1]" />
  23. </div>
  24. <div v-else-if="row.ranking" class="diamond">{{ row.ranking }}</div>
  25. </div>
  26. </template>
  27. <template #dim-allScore="{ row }">
  28. {{ row.allScore }}
  29. </template>
  30. </vxe-grid>
  31. <vxe-grid v-else v-bind="gridOptions" ref="gridRef1" class="mt-10">
  32. <template #imgUrl_default="{ row }">
  33. <div v-if="row.ranking <= 3" class="text-center">
  34. <img class="mt-auto" :src="RANK_IMG[Number(row.ranking) - 1]" />
  35. </div>
  36. <div v-else-if="row.ranking" class="diamond">{{ row.ranking }}</div>
  37. </template>
  38. <template #dim-allScore="{ row }">
  39. {{ row.allScore }}
  40. </template>
  41. </vxe-grid>
  42. </div>
  43. </template>
  44. <script setup>
  45. import { ElMessage } from "element-plus";
  46. import { ref, reactive, watch, onMounted, nextTick } from "vue";
  47. import rank1 from "@/assets/rank/rank1.png";
  48. import rank2 from "@/assets/rank/rank2.png";
  49. import rank3 from "@/assets/rank/rank3.png";
  50. import dayjs from "dayjs";
  51. import { useRoute } from "vue-router";
  52. const gridRef = ref(null);
  53. const gridRef1 = ref(null);
  54. // 导出数据
  55. const fileData = ref([]);
  56. const $route = useRoute();
  57. const RANK_IMG = [rank1, rank2, rank3];
  58. const deptName = ref("");
  59. const name = ref("");
  60. const mergeCells = ref([
  61. // 合并第一行前两列
  62. { row: 0, col: 0, rowspan: 1, colspan: 2 },
  63. { row: 2, col: 1, rowspan: 0, colspan: 0 }
  64. ]);
  65. const averageData = averageSore => {
  66. if (rawData.value.length > 0) {
  67. // let num = 0;
  68. // rawData.value.map(it => {
  69. // num = num + it.allScore;
  70. // });
  71. // num = num / rawData.value.length;
  72. rawData.value.unshift({
  73. dimensionList: [],
  74. allScore: rawData.value[0].averageSore,
  75. isAverageRow: true // 标记为平均分行
  76. });
  77. }
  78. };
  79. const selectYear = ref("2024-01-01");
  80. const gridOptions = reactive({
  81. border: true,
  82. loading: false,
  83. height: 500,
  84. sortConfig: {
  85. multiple: false
  86. },
  87. exportConfig: {},
  88. columns: [],
  89. data: [],
  90. showOverflow: true,
  91. showHeaderOverflow: true,
  92. showFooterOverflow: true,
  93. columnConfig: {
  94. resizable: true
  95. },
  96. scrollX: {
  97. enabled: true,
  98. gt: 0
  99. }
  100. });
  101. const tabTitle = ref();
  102. // 模拟接口数据
  103. const rawData = ref([]);
  104. const init = (item, deptNames, names) => {
  105. rawData.value = item;
  106. deptName.value = deptNames;
  107. name.value = names;
  108. if ($route.name != "workerDrak") {
  109. averageData(item.averageSore);
  110. }
  111. if (
  112. $route.name == "healthDrank" ||
  113. $route.name == "healthRank" ||
  114. $route.name == "healthDataDetail"
  115. ) {
  116. tabTitle.value = "医疗组";
  117. } else {
  118. tabTitle.value = "科室";
  119. }
  120. // 初始化
  121. createColumns();
  122. loadData();
  123. };
  124. defineExpose({
  125. init
  126. });
  127. // 判断key值是否相同返回具体值
  128. function getValueByKey(obj, key) {
  129. if (key in obj) {
  130. return obj[key];
  131. }
  132. return null; // 如果 key 不存在则返回 null
  133. }
  134. // 生成动态表头
  135. const createColumns = () => {
  136. let columns = [];
  137. console.log($route.name, "nnnnnn");
  138. if (
  139. $route.name == "workerDataDetail" ||
  140. $route.name === "headDataDetail" ||
  141. $route.name === "headDrank"
  142. ) {
  143. columns = [
  144. {
  145. title: "排名",
  146. field: "ranking",
  147. width: 70,
  148. slots: { default: "imgUrl_default" },
  149. fixed: "left" // 将此列固定在左侧
  150. },
  151. {
  152. title: "姓名",
  153. field: "assessmentObjectName",
  154. width: 100,
  155. fixed: "left" // 将此列固定在左侧
  156. },
  157. {
  158. title: "部门",
  159. field: "deptName",
  160. width: 150,
  161. fixed: "left" // 将此列固定在左侧
  162. },
  163. {
  164. title: "总得分",
  165. field: "allScore",
  166. sortable: true,
  167. width: 150,
  168. fixed: "right", // 将此列固定在右侧
  169. slots: { default: "dim-allScore" },
  170. sortBy({ column, row }) {
  171. // 如果是平均行(isAverageRow为true),则返回一个很大的值,避免被排序
  172. if (column.field in row && typeof row[column.field] === "number") {
  173. if (column.order == "asc") {
  174. if (row.isAverageRow) {
  175. return -9999999999;
  176. }
  177. } else {
  178. if (row.isAverageRow) {
  179. return 99999999999;
  180. }
  181. }
  182. } else {
  183. return -9999999999;
  184. }
  185. return row.allScore; // 返回正常的得分,进行排序
  186. }
  187. }
  188. ];
  189. } else {
  190. columns = [
  191. {
  192. title: "排名",
  193. field: "ranking",
  194. width: 70,
  195. slots: { default: "imgUrl_default" },
  196. fixed: "left" // 将此列固定在左侧
  197. },
  198. {
  199. title: tabTitle.value,
  200. field: "assessmentObjectName",
  201. width: 100,
  202. fixed: "left" // 将此列固定在左侧
  203. },
  204. {
  205. title: "总得分",
  206. field: "allScore",
  207. width: 150,
  208. sortable: true,
  209. fixed: "right", // 将此列固定在右侧
  210. slots: { default: "dim-allScore" },
  211. sortBy({ column, row }) {
  212. // 如果是平均行(isAverageRow为true),则返回一个很大的值,避免被排序
  213. if (column.field in row && typeof row[column.field] === "number") {
  214. if (column.order == "asc") {
  215. if (row.isAverageRow) {
  216. return -9999999999;
  217. }
  218. } else {
  219. if (row.isAverageRow) {
  220. return 99999999999;
  221. }
  222. }
  223. } else {
  224. return -9999999999;
  225. }
  226. return row.allScore; // 返回正常的得分,进行排序
  227. }
  228. }
  229. ];
  230. }
  231. const headerMap = new Map();
  232. // 遍历数据生成表头结构
  233. rawData.value.forEach(item => {
  234. item.dimensionList.forEach(dim => {
  235. if (!headerMap.has(dim.dimName)) {
  236. // 存储维度相关信息,包括 totalSore 和 dimWeight
  237. headerMap.set(dim.dimName, {
  238. dimWeight: dim.dimWeight || "0", // 默认值为 "-"
  239. totalSore: dim.totalSore || "0", // 默认值为 "-"
  240. quotas: [] // 用于存储 quota 列表
  241. });
  242. }
  243. dim.quotaList.forEach(quota => {
  244. const dimData = headerMap.get(dim.dimName);
  245. if (!dimData.quotas.some(q => q.quotaName === quota.quotaName)) {
  246. dimData.quotas.push({
  247. quotaName: quota.quotaName,
  248. quotaWeight: quota.quotaWeight || "0" // 默认值为 "-"
  249. });
  250. }
  251. });
  252. });
  253. });
  254. // 构建多级表头
  255. headerMap.forEach((dimData, dimName) => {
  256. const children = dimData.quotas.map(quota => ({
  257. title: `${quota.quotaName} ${quota.quotaWeight != "null" && quota.quotaWeight != "0" ? `(${quota.quotaWeight}%)` : ""}`, // 在 title 后拼接 quotaWeight
  258. width: 120,
  259. sortable: true,
  260. field: `${dimName}_${quota.quotaName}`, // 确保字段唯一
  261. sortBy({ column, row }) {
  262. // 如果是平均行(isAverageRow为true),则返回一个很大的值,避免被排序
  263. let aa = Number(row[column.field]);
  264. if (typeof aa === "number" && !isNaN(aa)) {
  265. if (column.order == "asc") {
  266. if (row.isAverageRow) {
  267. return -9999999999;
  268. }
  269. } else {
  270. if (row.isAverageRow) {
  271. return 99999999999;
  272. }
  273. }
  274. } else {
  275. // 倒序在最后
  276. if (column.order == "asc") {
  277. if (!row.isAverageRow) {
  278. return 9999999999999;
  279. } else {
  280. return -999999999999;
  281. }
  282. } else {
  283. if (!row.isAverageRow) {
  284. return -999999999999;
  285. } else {
  286. return 999999999999;
  287. }
  288. }
  289. }
  290. return getValueByKey(row, column.field); // 返回正常的得分,进行排序
  291. }
  292. }));
  293. // 添加维度名称及其对应的子列
  294. columns.push({
  295. title: `${dimName} ${dimData.dimWeight != "null" && dimData.dimWeight != "0" ? `(${dimData.dimWeight}%)` : ""}`, // 在 title 后拼接 dimWeight
  296. children
  297. });
  298. // 添加该维度的总分列
  299. columns.push({
  300. title: `总分`, // 可根据需要调整标题
  301. field: `${dimName}_totalSore`,
  302. width: 120,
  303. sortable: true,
  304. sortBy({ column, row }) {
  305. // 如果是平均行(isAverageRow为true),则返回一个很大的值,避免被排序
  306. let aa = Number(row[column.field]);
  307. if (typeof aa === "number" && !isNaN(aa)) {
  308. if (column.order == "asc") {
  309. if (row.isAverageRow) {
  310. return -9999999999;
  311. }
  312. } else {
  313. if (row.isAverageRow) {
  314. return 99999999999;
  315. }
  316. }
  317. } else {
  318. // 倒序在最后
  319. if (column.order == "asc") {
  320. if (!row.isAverageRow) {
  321. return 9999999999999;
  322. } else {
  323. return -999999999999;
  324. }
  325. } else {
  326. if (!row.isAverageRow) {
  327. return -999999999999;
  328. } else {
  329. return 999999999999;
  330. }
  331. }
  332. }
  333. return getValueByKey(row, column.field); // 返回正常的得分,进行排序
  334. }
  335. });
  336. });
  337. gridOptions.columns = columns;
  338. };
  339. // 加载数据
  340. // 保存原始数据副本
  341. const originalData = ref([]);
  342. const loadData = () => {
  343. gridOptions.loading = true;
  344. const tableData = rawData.value.map(item => {
  345. const row = {
  346. assessmentObjectName: item.assessmentObjectName,
  347. deptName: item.deptName,
  348. ranking: item.ranking,
  349. allScore: item.allScore,
  350. isAverageRow: item.isAverageRow || false // 确保数据里标记了是否为平均分行
  351. };
  352. item.dimensionList.forEach(dim => {
  353. let totalScore = 0;
  354. // 填充指标得分
  355. dim.quotaList.forEach(quota => {
  356. const fieldName = `${dim.dimName}_${quota.quotaName}`;
  357. row[fieldName] = quota.quotaScore;
  358. totalScore += quota.quotaScore;
  359. });
  360. // 填充维度总分
  361. const totalFieldName = `${dim.dimName}_totalSore`;
  362. row[totalFieldName] = dim.totalSore || totalScore;
  363. });
  364. return row;
  365. });
  366. originalData.value = [...tableData]; // 保存原始数据
  367. tableData[0].sortable = true;
  368. gridOptions.data = tableData;
  369. fileData.value = JSON.parse(JSON.stringify(tableData));
  370. if ($route.name != "workerDrak") {
  371. fileData.value[0].ranking = "平均得分";
  372. }
  373. // 使用 nextTick 确保表格渲染完成后再合并单元格
  374. nextTick(() => {
  375. if (gridRef.value) {
  376. gridRef.value.setMergeCells([
  377. { row: 0, col: 0, rowspan: 1, colspan: 2 },
  378. { row: 2, col: 1, rowspan: 0, colspan: 0 }
  379. ]);
  380. }
  381. });
  382. gridOptions.loading = false;
  383. };
  384. // 监听年份变化,重新渲染
  385. watch(selectYear, () => {
  386. // init(newVal.data, newVal.dept, newVal.name);
  387. createColumns();
  388. loadData();
  389. gridRef.value.setMergeCells([
  390. // 合并第一行前两列
  391. { row: 0, col: 0, rowspan: 1, colspan: 2 },
  392. { row: 2, col: 1, rowspan: 0, colspan: 0 }
  393. ]);
  394. });
  395. const setGridHeight = () => {
  396. // 设置表格的高度为设备的高度(例如,减去其他UI元素的高度)
  397. const height = window.innerHeight - 200; // 100 为你需要的偏移量(例如页头的高度)
  398. gridOptions.height = height;
  399. };
  400. onMounted(() => {
  401. setGridHeight();
  402. // 监听窗口大小变化,动态调整表格高度
  403. window.addEventListener("resize", setGridHeight);
  404. });
  405. // 导出
  406. const exportEvent = () => {
  407. let $grid;
  408. let fileName =
  409. localStorage.getItem("fileName") + dayjs().format("YYYY-MM-DD HH:mm:ss");
  410. if ($route.name != "workerDrak") {
  411. $grid = gridRef.value;
  412. } else {
  413. $grid = gridRef1.value;
  414. }
  415. if ($grid) {
  416. // gridOptions.exportConfig = {
  417. // type: "xlsx",
  418. // fileName: name
  419. // };
  420. $grid.exportData({
  421. type: "xlsx",
  422. filename: fileName,
  423. useStyle: true,
  424. isMerge: true,
  425. data: fileData.value
  426. });
  427. // $grid.exportData({
  428. // type: "xlsx",
  429. // fileName: name
  430. // });
  431. } else {
  432. ElMessage({
  433. message: "暂无数据",
  434. type: "warning"
  435. });
  436. }
  437. };
  438. </script>
  439. <style lang="scss" scoped>
  440. .diamond {
  441. display: flex;
  442. align-items: center;
  443. justify-content: center;
  444. width: 25px;
  445. /* 菱形宽度 */
  446. height: 25px;
  447. /* 菱形高度 */
  448. padding-top: 1px;
  449. margin: auto;
  450. clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  451. /* 定义六边形的形状 */
  452. font-size: 12px;
  453. color: white;
  454. background-color: #d3e8f0;
  455. /* 背景颜色 */
  456. }
  457. .mytTable ::v-deep .el-table th.el-table__cell,
  458. ::v-deep .el-table th,
  459. ::v-deep .el-table tr {
  460. color: white;
  461. background-color: transparent !important;
  462. }
  463. .aaaa {
  464. background-color: black !important;
  465. }
  466. .aa {
  467. position: relative;
  468. }
  469. </style>