1191 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			1191 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <script lang="ts">
 | |
| export default {
 | |
|   name: "LayTable",
 | |
| };
 | |
| </script>
 | |
| 
 | |
| <script setup lang="ts">
 | |
| import "./index.less";
 | |
| import {
 | |
|   ref,
 | |
|   watch,
 | |
|   useSlots,
 | |
|   withDefaults,
 | |
|   onMounted,
 | |
|   StyleValue,
 | |
|   WritableComputedRef,
 | |
|   computed,
 | |
|   onBeforeUnmount,
 | |
| } from "vue";
 | |
| import { Recordable } from "../../types";
 | |
| import LayCheckbox from "../checkbox/index.vue";
 | |
| import LayDropdown from "../dropdown/index.vue";
 | |
| import LayEmpty from "../empty/index.vue";
 | |
| import TableRow from "./TableRow.vue";
 | |
| import TablePage from "./TablePage.vue";
 | |
| import { nextTick } from "vue";
 | |
| import soultable from "./soultable.vue";
 | |
| 
 | |
| export interface TableProps {
 | |
|   id?: string;
 | |
|   skin?: string;
 | |
|   size?: string;
 | |
|   page?: Recordable;
 | |
|   columns: Recordable[];
 | |
|   dataSource: Recordable[];
 | |
|   defaultToolbar?: boolean | any[];
 | |
|   selectedKey?: string;
 | |
|   selectedKeys?: Recordable[];
 | |
|   indentSize?: number;
 | |
|   childrenColumnName?: string;
 | |
|   height?: number;
 | |
|   maxHeight?: string;
 | |
|   even?: boolean;
 | |
|   expandIndex?: number;
 | |
|   rowClassName?: string | Function;
 | |
|   cellClassName?: string | Function;
 | |
|   rowStyle?: string | Function;
 | |
|   cellStyle?: string | Function;
 | |
|   spanMethod?: Function;
 | |
|   defaultExpandAll?: boolean;
 | |
|   expandKeys?: Recordable[];
 | |
|   loading?: boolean;
 | |
|   getCheckboxProps?: Function;
 | |
|   getRadioProps?: Function;
 | |
|   download?: string;
 | |
|   serverpage?: boolean;
 | |
| }
 | |
| 
 | |
| const props = withDefaults(defineProps<TableProps>(), {
 | |
|   id: "id",
 | |
|   size: "md",
 | |
|   indentSize: 30,
 | |
|   childrenColumnName: "children",
 | |
|   dataSource: () => [],
 | |
|   selectedKeys: () => [],
 | |
|   defaultToolbar: false,
 | |
|   selectedKey: "",
 | |
|   maxHeight: "auto",
 | |
|   even: false,
 | |
|   rowClassName: "",
 | |
|   cellClassName: "",
 | |
|   expandIndex: 0,
 | |
|   rowStyle: "",
 | |
|   cellStyle: "",
 | |
|   defaultExpandAll: false,
 | |
|   spanMethod: () => {},
 | |
|   expandKeys: () => [],
 | |
|   loading: false,
 | |
|   getCheckboxProps: () => {},
 | |
|   getRadioProps: () => {},
 | |
|   download: "",
 | |
|   serverpage: false,
 | |
| });
 | |
| 
 | |
| const emit = defineEmits([
 | |
|   "change",
 | |
|   "update:expandKeys",
 | |
|   "update:selectedKeys",
 | |
|   "update:selectedKey",
 | |
|   "row-contextmenu",
 | |
|   "row-double",
 | |
|   "row",
 | |
|   "update:page",
 | |
| ]);
 | |
| 
 | |
| const slot = useSlots();
 | |
| const slots = slot.default && slot.default();
 | |
| const tableRef = ref();
 | |
| 
 | |
| const datalist = ref([...props.dataSource]);
 | |
| console.log(datalist.value, 97);
 | |
| console.log("表格初始化", 97);
 | |
| const s = "";
 | |
| const allChecked = ref(false);
 | |
| const hasChecked = ref(false);
 | |
| const tableDataSource = ref<any[]>([...props.dataSource]);
 | |
| const tableColumns = computed(() => {
 | |
|   return [...props.columns];
 | |
| });
 | |
| 
 | |
| const tableHeadColumns = ref<any[]>([]);
 | |
| const tableBodyColumns = ref<any[]>([]);
 | |
| 
 | |
| /**
 | |
|  * 获取数组深度
 | |
|  *
 | |
|  * @param arr 数组
 | |
|  */
 | |
| const getLevel = (arr: any[]) => {
 | |
|   let maxLevel = 0;
 | |
|   (function callBack(arr, level) {
 | |
|     ++level;
 | |
|     maxLevel = Math.max(level, maxLevel);
 | |
|     for (let i = 0; i < arr.length; i++) {
 | |
|       let item = arr[i];
 | |
|       if (item.children && item.children.length > 0) {
 | |
|         callBack(item.children, level);
 | |
|       } else {
 | |
|         delete item.children;
 | |
|       }
 | |
|     }
 | |
|   })(arr, 0);
 | |
|   return maxLevel;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * 获取叶节点的数量
 | |
|  *
 | |
|  * @param json 当前节点
 | |
|  */
 | |
| function getLeafCountTree(json: any) {
 | |
|   if (!json.children || json.children.length == 0) {
 | |
|     json.colspan = 1;
 | |
|     return 1;
 | |
|   } else {
 | |
|     var leafCount = 0;
 | |
|     for (var i = 0; i < json.children.length; i++) {
 | |
|       leafCount = leafCount + getLeafCountTree(json.children[i]);
 | |
|     }
 | |
|     json.colspan = leafCount;
 | |
|     return leafCount;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 计算内容列
 | |
|  *
 | |
|  * @param columns 原始列
 | |
|  */
 | |
| const findFindNode = (columns: any[]) => {
 | |
|   columns.forEach((column) => {
 | |
|     if (column.children) {
 | |
|       findFindNode(column.children);
 | |
|     } else {
 | |
|       tableBodyColumns.value.push(column);
 | |
|     }
 | |
|   });
 | |
| };
 | |
| 
 | |
| findFindNode(tableColumns.value);
 | |
| 
 | |
| /**
 | |
|  * 计算显示列
 | |
|  *
 | |
|  * @param columns 原始列
 | |
|  */
 | |
| const tableColumnKeys = ref<any[]>([]);
 | |
| 
 | |
| const findFindNodes = (columns: any[]) => {
 | |
|   columns.forEach((column) => {
 | |
|     if (column.children) {
 | |
|       tableColumnKeys.value.push(column.key);
 | |
|       findFindNodes(column.children);
 | |
|     } else {
 | |
|       if (!column.hide) {
 | |
|         tableColumnKeys.value.push(column.key);
 | |
|       }
 | |
|     }
 | |
|   });
 | |
| };
 | |
| 
 | |
| findFindNodes(tableColumns.value);
 | |
| 
 | |
| /**
 | |
|  * 计算数组差异
 | |
|  *
 | |
|  * @param arr1 数组
 | |
|  * @param arr2 数组
 | |
|  */
 | |
| function diff(arr1: any[], arr2: any[]) {
 | |
|   var newArr = [];
 | |
|   arr1 = Array.from(new Set(arr1)); // 去重
 | |
|   arr2 = Array.from(new Set(arr2)); // 去重
 | |
|   newArr = arr1.concat(arr2);
 | |
|   return newArr.filter((x) => !(arr1.includes(x) && arr2.includes(x)));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 计算标题列
 | |
|  *
 | |
|  * @param level 层级, 用于决定会被 push 到的目标数组
 | |
|  */
 | |
| const findFinalNode = (level: number, columns: any[]) => {
 | |
|   columns.forEach((column) => {
 | |
|     if (column.children) {
 | |
|       const colSpan = getLeafCountTree(column);
 | |
|       column.colspan = colSpan;
 | |
|       if (!tableHeadColumns.value[level]) {
 | |
|         tableHeadColumns.value[level] = [];
 | |
|       }
 | |
|       // 如果列固定,并且 width 不存在, 设置默认值
 | |
|       if (column.fixed && !column.width) {
 | |
|         column.type ? (column.width = "50px") : (column.width = "100px");
 | |
|       }
 | |
|       tableHeadColumns.value[level].push(column);
 | |
|       findFinalNode(level + 1, column.children);
 | |
|     } else {
 | |
|       const rowSpan = getLevel(columns);
 | |
|       column.rowspan = rowSpan;
 | |
|       if (!tableHeadColumns.value[level]) {
 | |
|         tableHeadColumns.value[level] = [];
 | |
|       }
 | |
|       // 如果列固定,并且 width 不存在, 设置默认值
 | |
|       if (column.fixed && !column.width) {
 | |
|         column.type ? (column.width = "50px") : (column.width = "100px");
 | |
|       }
 | |
|       tableHeadColumns.value[level].push(column);
 | |
|     }
 | |
|   });
 | |
| };
 | |
| 
 | |
| findFinalNode(0, tableColumns.value);
 | |
| 
 | |
| // 你浅拷贝他干什么呀
 | |
| const tableSelectedKeys = ref<Recordable[]>(props.selectedKeys);
 | |
| const tableExpandKeys = ref<Recordable[]>([...props.expandKeys]);
 | |
| 
 | |
| watch(
 | |
|   () => props.selectedKeys,
 | |
|   () => {
 | |
|     tableSelectedKeys.value = props.selectedKeys;
 | |
|   },
 | |
|   { deep: true }
 | |
| );
 | |
| 
 | |
| watch(
 | |
|   () => props.expandKeys,
 | |
|   () => {
 | |
|     tableExpandKeys.value = props.expandKeys;
 | |
|   },
 | |
|   { deep: true }
 | |
| );
 | |
| 
 | |
| const tableSelectedKey: WritableComputedRef<string> = computed({
 | |
|   get() {
 | |
|     return props.selectedKey;
 | |
|   },
 | |
|   set(val) {
 | |
|     emit("update:selectedKey", val);
 | |
|   },
 | |
| });
 | |
| 
 | |
| watch(
 | |
|   () => props.dataSource,
 | |
|   () => {
 | |
|     console.log("table数据更新");
 | |
|     tableDataSource.value = [...props.dataSource];
 | |
|     // if(!props.page){
 | |
|     //   datalist.value = [...props.dataSource]
 | |
|     // }else{
 | |
|     //   change({
 | |
|     //     limit: props.page.limit,
 | |
|     //     current: pagecurrent || props.page.current
 | |
|     //   })
 | |
|     // }
 | |
| 
 | |
|     // 为什么修改数据就要把选中清空? 我要清空不会自己清吗?
 | |
|     // tableSelectedKeys.value = [];
 | |
|     // tableSelectedKey.value = s;
 | |
|   },
 | |
|   { deep: true }
 | |
| );
 | |
| 
 | |
| const changeAll = (isChecked: boolean) => {
 | |
|   if (isChecked) {
 | |
|     const datasources = datalist.value.filter((item: any, index: number) => {
 | |
|       return !props.getCheckboxProps(item, index)?.disabled;
 | |
|     });
 | |
|     const ids = datasources.map((item) => {
 | |
|       return item[props.id];
 | |
|     });
 | |
|     tableSelectedKeys.value = [...ids];
 | |
|   } else {
 | |
|     tableSelectedKeys.value = [];
 | |
|   }
 | |
| };
 | |
| 
 | |
| watch(
 | |
|   tableSelectedKeys,
 | |
|   () => {
 | |
|     if (tableSelectedKeys.value.length === props.dataSource.length) {
 | |
|       allChecked.value = true;
 | |
|     } else {
 | |
|       allChecked.value = false;
 | |
|     }
 | |
|     if (tableSelectedKeys.value.length > 0) {
 | |
|       hasChecked.value = true;
 | |
|     } else {
 | |
|       hasChecked.value = false;
 | |
|     }
 | |
|     if (tableSelectedKeys.value != props.selectedKeys) {
 | |
|       console.log(
 | |
|         "初始化赋值触发修改",
 | |
|         tableSelectedKeys.value,
 | |
|         props.selectedKeys
 | |
|       );
 | |
|       emit("update:selectedKeys", tableSelectedKeys.value);
 | |
|     }
 | |
|   },
 | |
|   { deep: true, immediate: true }
 | |
| );
 | |
| 
 | |
| watch(
 | |
|   tableExpandKeys,
 | |
|   () => {
 | |
|     emit("update:expandKeys", tableExpandKeys.value);
 | |
|   },
 | |
|   { deep: true, immediate: true }
 | |
| );
 | |
| watch(tableDataSource, () => {
 | |
|   if (!props.page || props.serverpage) {
 | |
|     datalist.value = tableDataSource.value;
 | |
|   } else {
 | |
|     // props.page.count = tableDataSource.value.length
 | |
|     let tmp = { ...props.page };
 | |
|     tmp.total = tableDataSource.value.length;
 | |
|     emit("update:page", tmp);
 | |
|     // tableDataSource.value = endlist
 | |
|     sxlist.value = {};
 | |
|     // pagecurrent ||
 | |
|     change({
 | |
|       limit: props.page.limit,
 | |
|       current: props.page.current,
 | |
|     });
 | |
|   }
 | |
| });
 | |
| let pagecurrent: number;
 | |
| const change = function (page: any) {
 | |
|   if (props.serverpage) {
 | |
|     emit("change", page);
 | |
|   } else {
 | |
|     pagecurrent = page.current;
 | |
|     datalist.value = tableDataSource.value.slice(
 | |
|       page.limit * (page.current - 1),
 | |
|       page.limit * page.current
 | |
|     );
 | |
|   }
 | |
| };
 | |
| 
 | |
| const rowClick = function (data: any, evt: MouseEvent) {
 | |
|   emit("row", data, evt);
 | |
| };
 | |
| 
 | |
| const rowDoubleClick = function (data: any, evt: MouseEvent) {
 | |
|   emit("row-double", data, evt);
 | |
| };
 | |
| 
 | |
| const rowContextmenu = (data: any, evt: MouseEvent) => {
 | |
|   emit("row-contextmenu", data, evt);
 | |
| };
 | |
| 
 | |
| // 页面打印
 | |
| const print = () => {
 | |
|   let subOutputRankPrint = tableRef.value as HTMLElement;
 | |
|   let newContent = subOutputRankPrint.innerHTML;
 | |
|   let oldContent = document.body.innerHTML;
 | |
|   document.body.innerHTML = newContent;
 | |
|   window.print();
 | |
|   window.location.reload();
 | |
|   document.body.innerHTML = oldContent;
 | |
| };
 | |
| 
 | |
| // 报表导出
 | |
| const exportData = () => {
 | |
|   var tableStr = ``;
 | |
|   for (let tableHeadColumn of tableHeadColumns.value) {
 | |
|     tableStr += "<tr>";
 | |
|     for (let column of tableHeadColumn) {
 | |
|       tableStr += `<td colspan=${column.colspan} rowspan=${column.rowspan}>${column.title}</td>`;
 | |
|     }
 | |
|     tableStr += "</tr>";
 | |
|   }
 | |
|   tableDataSource.value.forEach((item, rowIndex) => {
 | |
|     tableStr += "<tr>";
 | |
|     tableBodyColumns.value.forEach((tableColumn, columnIndex) => {
 | |
|       Object.keys(item).forEach((name) => {
 | |
|         if (tableColumn.key === name) {
 | |
|           const rowColSpan = props.spanMethod(
 | |
|             item,
 | |
|             tableColumn,
 | |
|             rowIndex,
 | |
|             columnIndex
 | |
|           );
 | |
|           const rowspan = rowColSpan ? rowColSpan[0] : 1;
 | |
|           const colspan = rowColSpan ? rowColSpan[1] : 1;
 | |
|           if (rowspan != 0 && colspan != 0) {
 | |
|             tableStr += `<td colspan=${colspan} rowspan=${rowspan}>${item[name] || ''}</td>`;
 | |
|           }
 | |
|         }
 | |
|     
 | |
|       });
 | |
|       if(tableColumn.type == "number"){
 | |
|           const rowColSpan = props.spanMethod(
 | |
|             item,
 | |
|             tableColumn,
 | |
|             rowIndex,
 | |
|             columnIndex
 | |
|           );
 | |
|           const rowspan = rowColSpan ? rowColSpan[0] : 1;
 | |
|           const colspan = rowColSpan ? rowColSpan[1] : 1;
 | |
|           if (rowspan != 0 && colspan != 0) {
 | |
|             tableStr += `<td colspan=${colspan} rowspan=${rowspan}${tableColumn.valueType == 'str' ? ' x:str' : ''}>${rowIndex + 1}</td>`;
 | |
|           }
 | |
|         }
 | |
|     });
 | |
|     tableStr += "</tr>";
 | |
|   });
 | |
|   var worksheet = "Sheet1";
 | |
|   var uri = "data:application/vnd.ms-excel;base64,";
 | |
|   var exportTemplate = `<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" 
 | |
|         xmlns="http://www.w3.org/TR/REC-html40">
 | |
|         <head><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet>
 | |
|             <x:Name>${worksheet}</x:Name>
 | |
|                 <x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions></x:ExcelWorksheet>
 | |
|             </x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]-->
 | |
|         </head>
 | |
|         <body>
 | |
|             <table syle="table-layout: fixed;word-wrap: break-word; word-break: break-all;">${tableStr}</table>
 | |
|         </body>
 | |
|     </html>`;
 | |
|   let a = document.createElement("a");
 | |
|   a.href = uri + base64(exportTemplate);
 | |
|   a.download = (props.download || "下载文件") + ".xls";
 | |
|   a.click();
 | |
|   // window.location.href =
 | |
|   return;
 | |
| };
 | |
| 
 | |
| // BASE64编码
 | |
| function base64(s: string) {
 | |
|   return window.btoa(unescape(encodeURIComponent(s)));
 | |
| }
 | |
| 
 | |
| // 列排序
 | |
| const sortTable = (e: any, key: string, sort: string, issoul = false) => {
 | |
|   let currentSort = e.target.parentNode.getAttribute("lay-sort");
 | |
|   if (sort === "desc") {
 | |
|     if (currentSort === sort && !issoul) {
 | |
|       e.target.parentNode.setAttribute("lay-sort", "");
 | |
|       tableDataSource.value = [...props.dataSource];
 | |
|     } else {
 | |
|       e.target.parentNode.setAttribute("lay-sort", "desc");
 | |
|       tableDataSource.value.sort((x, y) => {
 | |
|         if (x[key] < y[key]) return 1;
 | |
|         else if (x[key] > y[key]) return -1;
 | |
|         else return 0;
 | |
|       });
 | |
|     }
 | |
|   } else {
 | |
|     if (currentSort === sort && !issoul) {
 | |
|       e.target.parentNode.setAttribute("lay-sort", "");
 | |
|       tableDataSource.value = [...props.dataSource];
 | |
|     } else {
 | |
|       e.target.parentNode.setAttribute("lay-sort", "asc");
 | |
|       tableDataSource.value.sort((x, y) => {
 | |
|         if (x[key] < y[key]) return -1;
 | |
|         else if (x[key] > y[key]) return 1;
 | |
|         else return 0;
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| let tableBody = ref<HTMLElement | null>(null);
 | |
| let tableHeader = ref<HTMLElement | null>(null);
 | |
| let tableHeaderTable = ref<HTMLElement | null>(null);
 | |
| const tableBodyEmptyWidth = ref();
 | |
| let scrollWidthCell = ref(0);
 | |
| 
 | |
| const getScrollWidth = () => {
 | |
|   const clientWidth: number = tableBody.value?.clientWidth || 0;
 | |
|   const offsetWidth: number = tableBody.value?.offsetWidth || 0;
 | |
|   if (clientWidth < offsetWidth) {
 | |
|     scrollWidthCell.value = offsetWidth - clientWidth;
 | |
|   } else {
 | |
|     scrollWidthCell.value = 0;
 | |
|   }
 | |
| 
 | |
|   tableBodyEmptyWidth.value = tableHeaderTable.value?.offsetWidth + "px";
 | |
| };
 | |
| 
 | |
| const hasl = ref(false);
 | |
| const hasr = ref(false);
 | |
| 
 | |
| const classes = computed(() => {
 | |
|   return [
 | |
|     hasl.value ? "layui-table-has-fixed-left" : "",
 | |
|     hasr.value ? "layui-table-has-fixed-right" : "",
 | |
|   ];
 | |
| });
 | |
| 
 | |
| watch(
 | |
|   () => [props.height, props.maxHeight, props.dataSource],
 | |
|   () => {
 | |
|     nextTick(() => {
 | |
|       getScrollWidth();
 | |
|       getFixedColumn();
 | |
|     });
 | |
|   },
 | |
|   {
 | |
|     deep: true,
 | |
|   }
 | |
| );
 | |
| 
 | |
| onMounted(() => {
 | |
|   getScrollWidth();
 | |
|   getFixedColumn();
 | |
| 
 | |
|   tableBody.value?.addEventListener("scroll", () => {
 | |
|     getFixedColumn();
 | |
|   });
 | |
| 
 | |
|   tableBody.value?.addEventListener("transitionend", () => {
 | |
|     getScrollWidth();
 | |
|   });
 | |
| 
 | |
|   window.onresize = () => {
 | |
|     getScrollWidth();
 | |
|     getFixedColumn();
 | |
|   };
 | |
| });
 | |
| 
 | |
| const getFixedColumn = () => {
 | |
|   tableHeader.value!.scrollLeft = tableBody.value?.scrollLeft || 0;
 | |
|   // @ts-ignore
 | |
|   if (tableBody.value?.scrollWidth > tableBody.value?.clientWidth) {
 | |
|     if (tableBody.value?.scrollLeft == 0) {
 | |
|       hasl.value = false;
 | |
|       hasr.value = true;
 | |
|     } else {
 | |
|       // @ts-ignore
 | |
|       const t = tableBody.value?.scrollLeft + tableBody.value?.offsetWidth + 2;
 | |
|       const s = tableBody.value?.scrollWidth;
 | |
|       // @ts-ignore
 | |
|       if (t > s) {
 | |
|         hasl.value = true;
 | |
|         hasr.value = false;
 | |
|       } else {
 | |
|         hasl.value = true;
 | |
|         hasr.value = true;
 | |
|       }
 | |
|     }
 | |
|   } else {
 | |
|     hasl.value = false;
 | |
|     hasr.value = false;
 | |
|   }
 | |
| };
 | |
| 
 | |
| const slotsData = ref<string[]>([]);
 | |
| 
 | |
| props.columns.map((value: any) => {
 | |
|   if (value.customSlot) {
 | |
|     slotsData.value.push(value.customSlot);
 | |
|   }
 | |
| });
 | |
| 
 | |
| const currentIndentSize = ref(0);
 | |
| const childrenExpandSpace = computed(() => {
 | |
|   return (
 | |
|     props.dataSource.find((value: any) => {
 | |
|       if (value[props.childrenColumnName]) {
 | |
|         return true;
 | |
|       }
 | |
|     }) != undefined
 | |
|   );
 | |
| });
 | |
| 
 | |
| const renderFixedStyle = (column: any, columnIndex: number) => {
 | |
|   if (column.fixed) {
 | |
|     if (column.fixed == "left") {
 | |
|       var left = 0;
 | |
|       for (var i = 0; i < columnIndex; i++) {
 | |
|         if (
 | |
|           props.columns[i].fixed &&
 | |
|           props.columns[i].fixed == "left" &&
 | |
|           tableColumnKeys.value.includes(props.columns[i].key)
 | |
|         ) {
 | |
|           left = left + Number(props.columns[i]?.width?.replace("px", ""));
 | |
|         }
 | |
|       }
 | |
|       return { left: `${left}px` } as StyleValue;
 | |
|     } else {
 | |
|       var right = 0;
 | |
|       for (var i = columnIndex + 1; i < props.columns.length; i++) {
 | |
|         if (
 | |
|           props.columns[i].fixed &&
 | |
|           props.columns[i].fixed == "right" &&
 | |
|           tableColumnKeys.value.includes(props.columns[i].key)
 | |
|         ) {
 | |
|           right = right + Number(props.columns[i]?.width?.replace("px", ""));
 | |
|         }
 | |
|       }
 | |
|       return { right: `${right}px` } as StyleValue;
 | |
|     }
 | |
|   } else {
 | |
|     var isLast = true;
 | |
|     for (var i = columnIndex + 1; i < props.columns.length; i++) {
 | |
|       if (
 | |
|         props.columns[i].fixed == undefined &&
 | |
|         tableColumnKeys.value.includes(props.columns[i].key)
 | |
|       ) {
 | |
|         isLast = false;
 | |
|       }
 | |
|     }
 | |
|     return isLast ? ({ "border-right": "none" } as StyleValue) : {};
 | |
|   }
 | |
|   return {} as StyleValue;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @remark 排除 hide 列
 | |
|  */
 | |
| const renderHeadFixedStyle = (
 | |
|   column: any,
 | |
|   columnIndex: number,
 | |
|   tableHeadColumn: any[]
 | |
| ) => {
 | |
|   if (column.fixed) {
 | |
|     if (column.fixed == "left") {
 | |
|       var left = 0;
 | |
|       for (var i = 0; i < columnIndex; i++) {
 | |
|         if (
 | |
|           props.columns[i].fixed &&
 | |
|           props.columns[i].fixed == "left" &&
 | |
|           tableColumnKeys.value.includes(props.columns[i].key)
 | |
|         ) {
 | |
|           left = left + Number(props.columns[i]?.width?.replace("px", ""));
 | |
|         }
 | |
|       }
 | |
|       return { left: `${left}px` } as StyleValue;
 | |
|     } else {
 | |
|       var right = 0;
 | |
|       for (var i = columnIndex + 1; i < props.columns.length; i++) {
 | |
|         if (
 | |
|           props.columns[i].fixed &&
 | |
|           props.columns[i].fixed == "right" &&
 | |
|           tableColumnKeys.value.includes(props.columns[i].key)
 | |
|         ) {
 | |
|           right = right + Number(props.columns[i]?.width?.replace("px", ""));
 | |
|         }
 | |
|       }
 | |
|       return { right: `${right}px` } as StyleValue;
 | |
|     }
 | |
|   } else {
 | |
|     var isLast = true;
 | |
|     for (var i = columnIndex + 1; i < tableHeadColumn.length; i++) {
 | |
|       if (
 | |
|         tableHeadColumn[i].fixed == undefined &&
 | |
|         tableColumnKeys.value.includes(tableHeadColumn[i].key)
 | |
|       ) {
 | |
|         isLast = false;
 | |
|       }
 | |
|     }
 | |
|     return isLast ? ({ "border-right": "none" } as StyleValue) : {};
 | |
|   }
 | |
|   return {} as StyleValue;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * @remark 排除 hide 列
 | |
|  */
 | |
| const renderFixedClassName = (column: any, columnIndex: number) => {
 | |
|   if (column.fixed) {
 | |
|     if (column.fixed == "left") {
 | |
|       var left = true;
 | |
|       for (var i = columnIndex + 1; i < props.columns.length; i++) {
 | |
|         if (
 | |
|           props.columns[i].fixed &&
 | |
|           props.columns[i].fixed == "left" &&
 | |
|           tableColumnKeys.value.includes(props.columns[i].key)
 | |
|         ) {
 | |
|           left = false;
 | |
|         }
 | |
|       }
 | |
|       return left ? `layui-table-fixed-left-last` : "";
 | |
|     } else {
 | |
|       var right = true;
 | |
|       for (var i = 0; i < columnIndex; i++) {
 | |
|         if (
 | |
|           props.columns[i].fixed &&
 | |
|           props.columns[i].fixed == "right" &&
 | |
|           tableColumnKeys.value.includes(props.columns[i].key)
 | |
|         ) {
 | |
|           right = false;
 | |
|         }
 | |
|       }
 | |
|       return right ? `layui-table-fixed-right-first` : "";
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| const hasTotalRow = computed(() => {
 | |
|   let b = false;
 | |
|   props.columns.forEach((item) => {
 | |
|     if (item.totalRow) {
 | |
|       b = true;
 | |
|     }
 | |
|   });
 | |
|   return b;
 | |
| });
 | |
| 
 | |
| const renderTotalRowCell = (column: any) => {
 | |
|   if (column.totalRow) {
 | |
|     if (column.totalRow != true) {
 | |
|       return column.totalRow;
 | |
|     } else {
 | |
|       if (column.totalRowMethod) {
 | |
|         return column.totalRowMethod(column, tableDataSource.value);
 | |
|       } else {
 | |
|         return totalRowMethod(column, tableDataSource.value);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| };
 | |
| 
 | |
| const totalRowMethod = (column: any, dataSource: any[]) => {
 | |
|   let total = 0;
 | |
|   dataSource.forEach((item) => {
 | |
|     total = total + Number(item[column.key]);
 | |
|   });
 | |
|   return total;
 | |
| };
 | |
| 
 | |
| const showToolbar = (toolbarName: string) => {
 | |
|   if (props.defaultToolbar instanceof Array) {
 | |
|     return props.defaultToolbar.includes(toolbarName);
 | |
|   }
 | |
|   return props.defaultToolbar;
 | |
| };
 | |
| 
 | |
| const toolbarStyle = (toolbarName: string) => {
 | |
|   if (props.defaultToolbar instanceof Array) {
 | |
|     return { order: props.defaultToolbar.indexOf(toolbarName) } as StyleValue;
 | |
|   }
 | |
| };
 | |
| 
 | |
| onBeforeUnmount(() => {
 | |
|   window.onresize = null;
 | |
| });
 | |
| 
 | |
| // 模仿soultable
 | |
| const soulstatus = ref(false);
 | |
| const soultop = ref(0);
 | |
| const soulleft = ref(0);
 | |
| const selcolumn = ref<any>({});
 | |
| const soulkey = ref("");
 | |
| const sxlist: any = ref({});
 | |
| function showsoul(event: MouseEvent, column: any, key: string) {
 | |
|   console.log(event);
 | |
|   soulleft.value = event.pageX;
 | |
|   soultop.value = event.pageY;
 | |
|   soulstatus.value = true;
 | |
|   selcolumn.value = column;
 | |
|   soulkey.value = key;
 | |
| }
 | |
| const heddin = () => {
 | |
|   soulkey.value = "";
 | |
| };
 | |
| function asc(event: any) {
 | |
|   selcolumn.value.soulclass = "soul-icon-filter-asc";
 | |
|   sortTable(event, selcolumn.value.key, "asc", true);
 | |
| }
 | |
| function desc(event: any) {
 | |
|   selcolumn.value.soulclass = "soul-icon-filter-desc";
 | |
|   sortTable(event, selcolumn.value.key, "desc", true);
 | |
| }
 | |
| 
 | |
| function sx(e: any) {
 | |
|   sxlist.value[e.key] = e.list;
 | |
|   console.log("筛选事件", sxlist.value, sxlist.value.length);
 | |
| }
 | |
| watch(sxlist, () => {
 | |
|   console.log("sxlist更新");
 | |
| });
 | |
| watch(
 | |
|   [() => sxlist, () => props.dataSource],
 | |
|   (old, new1) => {
 | |
|     // tableDataSource
 | |
|     // console.log(JSON.stringify(sxlist.value),JSON.stringify(props.dataSource),old[0] == new1[0],old[1] == new1[1])
 | |
|     if (Object.keys(sxlist.value).length == 0) {
 | |
|       return;
 | |
|     }
 | |
|     let list: any = [...props.dataSource];
 | |
|     let endlist: any = [];
 | |
|     for (let i in sxlist.value) {
 | |
|       for (let j in list) {
 | |
|         if (list[j] != "" && sxlist.value[i].length != 0) {
 | |
|           if (!sxlist.value[i].includes(list[j][i])) {
 | |
|             list[j] = "";
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     for (let i of list) {
 | |
|       if (i != "") {
 | |
|         endlist.push(i);
 | |
|       }
 | |
|     }
 | |
|     if (!props.page || props.serverpage) {
 | |
|       nextTick(() => {
 | |
|         datalist.value = endlist;
 | |
|       });
 | |
|     } else {
 | |
|       tableDataSource.value = endlist;
 | |
|       // pagecurrent ||
 | |
|       change({
 | |
|         limit: props.page.limit,
 | |
|         current:  props.page.current,
 | |
|         isReload: true,
 | |
|       });
 | |
|     }
 | |
|   },
 | |
|   {
 | |
|     deep: true,
 | |
|   }
 | |
| );
 | |
| window.addEventListener("click", heddin);
 | |
| // 将分页移入到组件内
 | |
| </script>
 | |
| 
 | |
| <template>
 | |
|   <div ref="tableRef">
 | |
|     <table class="layui-hide" lay-filter="test"></table>
 | |
|     <div class="layui-form layui-border-box layui-table-view" :class="classes">
 | |
|       <div v-if="defaultToolbar || slot.toolbar" class="layui-table-tool">
 | |
|         <div class="layui-table-tool-temp">
 | |
|           <slot name="toolbar"></slot>
 | |
|         </div>
 | |
|         <div v-if="defaultToolbar" class="layui-table-tool-self">
 | |
|           <!-- 筛选 -->
 | |
|           <lay-dropdown
 | |
|             v-if="showToolbar('filter')"
 | |
|             updateAtScroll
 | |
|             :style="toolbarStyle('filter')"
 | |
|           >
 | |
|             <div class="layui-inline" title="筛选" lay-event>
 | |
|               <i class="layui-icon layui-icon-slider"></i>
 | |
|             </div>
 | |
|             <template #content>
 | |
|               <div class="layui-table-tool-checkbox">
 | |
|                 <lay-checkbox
 | |
|                   v-for="column in tableHeadColumns[0]"
 | |
|                   v-model="tableColumnKeys"
 | |
|                   skin="primary"
 | |
|                   :disabled="column.children"
 | |
|                   :key="column.key"
 | |
|                   :value="column.key"
 | |
|                   >{{ column.title }}</lay-checkbox
 | |
|                 >
 | |
|               </div>
 | |
|             </template>
 | |
|           </lay-dropdown>
 | |
| 
 | |
|           <!-- 导出 -->
 | |
|           <div
 | |
|             v-if="showToolbar('export')"
 | |
|             class="layui-inline"
 | |
|             title="导出"
 | |
|             lay-event
 | |
|             :style="toolbarStyle('export')"
 | |
|             @click="exportData()"
 | |
|           >
 | |
|             <i class="layui-icon layui-icon-export"></i>
 | |
|           </div>
 | |
| 
 | |
|           <!-- 打印 -->
 | |
|           <div
 | |
|             v-if="showToolbar('print')"
 | |
|             :style="toolbarStyle('print')"
 | |
|             class="layui-inline"
 | |
|             title="打印"
 | |
|             lay-event
 | |
|             @click="print()"
 | |
|           >
 | |
|             <i class="layui-icon layui-icon-print"></i>
 | |
|           </div>
 | |
|         </div>
 | |
|       </div>
 | |
| 
 | |
|       <div class="layui-table-box-header" v-if="slot.header">
 | |
|         <slot name="header"></slot>
 | |
|       </div>
 | |
| 
 | |
|       <div class="layui-table-box">
 | |
|         <!-- 表头 -->
 | |
|         <div
 | |
|           class="layui-table-header"
 | |
|           :style="[{ 'padding-right': `${scrollWidthCell}px` }]"
 | |
|         >
 | |
|           <div class="layui-table-header-wrapper" ref="tableHeader">
 | |
|             <table
 | |
|               class="layui-table"
 | |
|               :lay-size="size"
 | |
|               :lay-skin="skin"
 | |
|               ref="tableHeaderTable"
 | |
|             >
 | |
|               <colgroup>
 | |
|                 <template v-for="column in tableBodyColumns" :key="column">
 | |
|                   <template v-if="tableColumnKeys.includes(column.key)">
 | |
|                     <col
 | |
|                       :width="column.width"
 | |
|                       :style="{
 | |
|                         minWidth: column.minWidth ? column.minWidth : '50px',
 | |
|                       }"
 | |
|                     />
 | |
|                   </template>
 | |
|                 </template>
 | |
|               </colgroup>
 | |
|               <thead>
 | |
|                 <template
 | |
|                   v-for="(
 | |
|                     tableHeadColumn, tableHeadColumnIndex
 | |
|                   ) in tableHeadColumns"
 | |
|                   :key="tableHeadColumnIndex"
 | |
|                 >
 | |
|                   <tr>
 | |
|                     <template
 | |
|                       v-for="(column, columnIndex) in tableHeadColumn"
 | |
|                       :key="column"
 | |
|                     >
 | |
|                       <th
 | |
|                         v-if="tableColumnKeys.includes(column.key)"
 | |
|                         :colspan="column.colspan"
 | |
|                         :rowspan="column.rowspan"
 | |
|                         class="layui-table-cell"
 | |
|                         :class="[
 | |
|                           renderFixedClassName(column, columnIndex),
 | |
|                           column.fixed
 | |
|                             ? `layui-table-fixed-${column.fixed}`
 | |
|                             : '',
 | |
|                           column.type == 'checkbox'
 | |
|                             ? 'layui-table-cell-checkbox'
 | |
|                             : '',
 | |
|                           column.type == 'radio'
 | |
|                             ? 'layui-table-cell-radio'
 | |
|                             : '',
 | |
|                           column.type == 'number'
 | |
|                             ? 'layui-table-cell-number'
 | |
|                             : '',
 | |
|                         ]"
 | |
|                         :style="[
 | |
|                           {
 | |
|                             textAlign: column.align,
 | |
|                           },
 | |
|                           renderHeadFixedStyle(
 | |
|                             column,
 | |
|                             columnIndex,
 | |
|                             tableHeadColumn
 | |
|                           ),
 | |
|                         ]"
 | |
|                       >
 | |
|                         <template v-if="column.type == 'checkbox'">
 | |
|                           <lay-checkbox
 | |
|                             v-model="hasChecked"
 | |
|                             :is-indeterminate="!allChecked"
 | |
|                             skin="primary"
 | |
|                             value="all"
 | |
|                             @change="changeAll"
 | |
|                           />
 | |
|                         </template>
 | |
|                         <template v-else>
 | |
|                           <span>
 | |
|                             <template v-if="column.titleSlot">
 | |
|                               <slot :name="column.titleSlot"></slot>
 | |
|                             </template>
 | |
|                             <template v-else>
 | |
|                               {{ column.title }}
 | |
|                             </template>
 | |
|                           </span>
 | |
|                           <!-- 插槽 -->
 | |
|                           <span
 | |
|                             v-if="column.sort"
 | |
|                             class="layui-table-sort layui-inline"
 | |
|                             lay-sort
 | |
|                           >
 | |
|                             <i
 | |
|                               @click.stop="sortTable($event, column.key, 'asc')"
 | |
|                               class="layui-edge layui-table-sort-asc"
 | |
|                               title="升序"
 | |
|                             ></i>
 | |
|                             <i
 | |
|                               @click.stop="
 | |
|                                 sortTable($event, column.key, 'desc')
 | |
|                               "
 | |
|                               class="layui-edge layui-table-sort-desc"
 | |
|                               title="降序"
 | |
|                             ></i>
 | |
|                           </span>
 | |
|                           <span
 | |
|                             v-if="column.soul"
 | |
|                             class="layui-table-sort layui-inline soul-icon"
 | |
|                           >
 | |
|                             <i
 | |
|                               class="soul-icon soul-box"
 | |
|                               :class="column.soulclass || 'soul-icon-filter'"
 | |
|                               @click.stop="showsoul($event, column, column.key)"
 | |
|                             >
 | |
|                               <!-- <div v-show="column.soulshow" @click.stop="" class="soulbox">
 | |
|                                     11111
 | |
|                                   </div> -->
 | |
|                             </i>
 | |
|                           </span>
 | |
|                         </template>
 | |
|                       </th>
 | |
|                     </template>
 | |
|                   </tr>
 | |
|                 </template>
 | |
|               </thead>
 | |
|             </table>
 | |
|           </div>
 | |
|         </div>
 | |
|         <!-- 表身 -->
 | |
|         <div
 | |
|           class="layui-table-body layui-table-main"
 | |
|           :style="{ height: height, maxHeight: maxHeight }"
 | |
|           ref="tableBody"
 | |
|         >
 | |
|           <table
 | |
|             class="layui-table"
 | |
|             v-if="datalist.length > 0 && loading == false"
 | |
|             :class="{ 'layui-table-even': props.even }"
 | |
|             :lay-size="size"
 | |
|             :lay-skin="skin"
 | |
|           >
 | |
|             <colgroup>
 | |
|               <template
 | |
|                 v-for="(column, columnIndex) in tableBodyColumns"
 | |
|                 :key="columnIndex"
 | |
|               >
 | |
|                 <template v-if="tableColumnKeys.includes(column.key)">
 | |
|                   <col
 | |
|                     :width="column.width"
 | |
|                     :style="{
 | |
|                       minWidth: column.minWidth ? column.minWidth : '50px',
 | |
|                     }"
 | |
|                   />
 | |
|                 </template>
 | |
|               </template>
 | |
|             </colgroup>
 | |
|             <tbody>
 | |
|               <!-- 渲染 -->
 | |
|               <template v-for="(children, index) in datalist" :key="index">
 | |
|                 <table-row
 | |
|                   :id="id"
 | |
|                   :index="index"
 | |
|                   :data="children"
 | |
|                   :columns="tableBodyColumns"
 | |
|                   :indent-size="indentSize"
 | |
|                   :currentIndentSize="currentIndentSize"
 | |
|                   :tableColumnKeys="tableColumnKeys"
 | |
|                   :expandSpace="childrenExpandSpace"
 | |
|                   :expandIndex="expandIndex"
 | |
|                   :cellStyle="cellStyle"
 | |
|                   :cellClassName="cellClassName"
 | |
|                   :rowStyle="rowStyle"
 | |
|                   :rowClassName="rowClassName"
 | |
|                   :spanMethod="spanMethod"
 | |
|                   :defaultExpandAll="defaultExpandAll"
 | |
|                   :getCheckboxProps="getCheckboxProps"
 | |
|                   :getRadioProps="getRadioProps"
 | |
|                   v-model:expandKeys="tableExpandKeys"
 | |
|                   v-model:selectedKeys="tableSelectedKeys"
 | |
|                   v-model:selectedKey="tableSelectedKey"
 | |
|                   @row="rowClick"
 | |
|                   @row-double="rowDoubleClick"
 | |
|                   @row-contextmenu="rowContextmenu"
 | |
|                   :page="page"
 | |
|                 >
 | |
|                   <template v-for="name in slotsData" #[name]="{ data }">
 | |
|                     <slot :name="name" :data="data"></slot>
 | |
|                   </template>
 | |
|                   <template v-if="slot.expand" #expand="{ data }">
 | |
|                     <slot name="expand" :data="data"></slot>
 | |
|                   </template>
 | |
|                 </table-row>
 | |
|               </template>
 | |
|               <tr v-if="hasTotalRow" class="layui-table-total">
 | |
|                 <template
 | |
|                   v-for="(column, columnIndex) in columns"
 | |
|                   :key="columnIndex"
 | |
|                 >
 | |
|                   <template v-if="tableColumnKeys.includes(column.key)">
 | |
|                     <td
 | |
|                       :style="[
 | |
|                         {
 | |
|                           textAlign: column.align,
 | |
|                           whiteSpace: column.ellipsisTooltip
 | |
|                             ? 'nowrap'
 | |
|                             : 'normal',
 | |
|                         },
 | |
|                         renderFixedStyle(column, columnIndex),
 | |
|                       ]"
 | |
|                       :class="[
 | |
|                         'layui-table-cell',
 | |
|                         renderFixedClassName(column, columnIndex),
 | |
|                         column.fixed ? `layui-table-fixed-${column.fixed}` : '',
 | |
|                       ]"
 | |
|                       v-html="renderTotalRowCell(column)"
 | |
|                     ></td>
 | |
|                   </template>
 | |
|                 </template>
 | |
|               </tr>
 | |
|             </tbody>
 | |
|           </table>
 | |
|           <template v-if="datalist.length == 0 && loading == false">
 | |
|             <lay-empty></lay-empty>
 | |
|             <div :style="{ width: tableBodyEmptyWidth }"></div>
 | |
|           </template>
 | |
|           <template v-if="loading == true">
 | |
|             <div class="layui-table-loading">
 | |
|               <i
 | |
|                 class="layui-icon-loading layui-icon layui-anim layui-anim-rotate layui-anim-loop"
 | |
|               ></i>
 | |
|             </div>
 | |
|           </template>
 | |
|         </div>
 | |
|         <div class="layui-table-footer" v-if="slot.footer">
 | |
|           <slot name="footer"></slot>
 | |
|         </div>
 | |
|       </div>
 | |
|       <div v-if="page && page.total > 0" class="layui-table-page">
 | |
|         <table-page
 | |
|           :total="page.total"
 | |
|           :pages="page.pages"
 | |
|           :theme="page.theme"
 | |
|           :limits="page.limits"
 | |
|           :showSkip="page.showSkip"
 | |
|           :show-page="page.showPage"
 | |
|           :showRefresh="page.showRefresh"
 | |
|           :showLimit="page.showLimit"
 | |
|           :showCount="page.showCount"
 | |
|           :count="page.count"
 | |
|           v-model:current="page.current"
 | |
|           v-model:limit="page.limit"
 | |
|           @change="change"
 | |
|         >
 | |
|         </table-page>
 | |
|       </div>
 | |
|     </div>
 | |
|     <div
 | |
|       v-for="(tableHeadColumn, tableHeadColumnIndex) in tableHeadColumns"
 | |
|       :key="tableHeadColumnIndex"
 | |
|     >
 | |
|       <div v-for="(column, columnIndex) in tableHeadColumn" :key="column">
 | |
|         <soultable
 | |
|           :top="soultop"
 | |
|           :left="soulleft"
 | |
|           v-show="soulkey == column.key"
 | |
|           :show="soulkey == column.key"
 | |
|           @asc="asc"
 | |
|           @desc="desc"
 | |
|           @daochu="exportData"
 | |
|           :list="datalist"
 | |
|           :soulkey="column.key"
 | |
|           @sx="sx"
 | |
|         ></soultable>
 | |
|       </div>
 | |
|     </div>
 | |
|   </div>
 | |
| </template>
 |