index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. <template>
  2. <div class="containerTable">
  3. <div class="table_box">
  4. <div class="table-title-btn" v-if="$slots['table-title-btn']">
  5. <slot name="table-title-btn"></slot>
  6. </div>
  7. <!-- <OverlayScrollbarsComponent defer :options="{
  8. scrollbars: {
  9. autoHide: 'leave', // 鼠标离开时自动隐藏
  10. clickScroll: true, // 允许点击轨道滚动
  11. theme: 'my-theme', // 自定义主题
  12. }
  13. }"> -->
  14. <el-table ref="multipleTableRef" :row-key="rowKey"
  15. :header-cell-style="{ 'background-color': '#F7F7F9', 'border-bottom': '1px solid #EEF1F6', 'font-size': '15px', 'color': '#333', 'padding': '10px 0' }"
  16. :data="props.tableData" :height="tableHeight" :max-height="maxHeight" table-layout="fixed" style="width: 100%;"
  17. @selection-change="selectionChange" @select-all="selectAllChange" @cell-mouse-enter="handleMouseEnter"
  18. @cell-mouse-leave="handleMouseLeave" :highlight-current-row="highlightCurrentRow"
  19. @current-change="currentChange" v-loading="loading" :show-summary='props.showSummary' border>
  20. <!-- 空内容自定义 -->
  21. <template #empty>
  22. <div class="flex f-c a-c ">
  23. <img src="../../assets/images/empty.png" alt="">
  24. <span style="font-size: 14px;color: #909399;">暂无数据</span>
  25. </div>
  26. </template>
  27. <el-table-column type="selection" v-if="showSelect" :selectable="selectable" width="55" fixed="left" />
  28. <el-table-column type="expand" v-if="secondaryTable">
  29. <template #default="props">
  30. <div>
  31. <el-table :data="props.row.children">
  32. <el-table-column v-for="column in secondaryColumns" :key="column.prop" :prop="column.prop"
  33. :label="column.label" :sortable="column.sortable ? 'custom' : false" :width="column.width">
  34. <template #header>
  35. <slot name="search" :slotData="column"></slot>
  36. </template>
  37. <template #default="scope">
  38. <RouterLink v-if="column.component === 'RouterLink'" :to="getRouterLinkTo(scope.row, column)"
  39. class="custom-link">
  40. {{ scope.row[column.prop] }}
  41. </RouterLink>
  42. <el-tag v-else-if="column.component === 'Tag'" :type="getTagTypeAndText(scope.row, column).type">
  43. {{ getTagTypeAndText(scope.row, column).text }}
  44. </el-tag>
  45. <ImagePreview v-else-if="column.component === 'Image'" :src="getImgTo(scope.row, column)"
  46. width="50px" height="50px"></ImagePreview>
  47. <template v-else>
  48. {{ getDisplayText(scope.row, column) }}
  49. </template>
  50. </template>
  51. </el-table-column>
  52. <el-table-column label="操作" fixed="right" :width="props.columnwidth">
  53. <template #default="scope">
  54. <slot name="secondary" :secondaryData="scope.row"></slot>
  55. </template>
  56. </el-table-column>
  57. </el-table>
  58. </div>
  59. </template>
  60. </el-table-column>
  61. <el-table-column v-if="showIndex" type="index" label="序号" width="55" />
  62. <el-table-column v-for="column in columns" :key="column.prop" :prop="column.prop" :label="column.label"
  63. :sortable="column.sortable ? 'custom' : false" :width="column.width" :filters="column.filters"
  64. :filter-method="column.filters ? (value, row) => filterMethod(value, row, column) : undefined"
  65. :filtered-value="column.filteredValue ? column.filteredValue : undefined" :fixed="column.fixed || false"
  66. :align="column.align ? column.align : 'center'" show-overflow-tooltip>
  67. <template #header>
  68. <slot name="search" :slotData="column"></slot>
  69. </template>
  70. <template #default="scope" v-if="column.component === 'customSlot'">
  71. <slot name="customSlot" :data="scope.row" :bodyCell="{ scope, column }"></slot>
  72. <slot :name="column.prop" :data="scope.row" :bodyCell="{ scope, column }"></slot>
  73. </template>
  74. <template #default="scope" v-else>
  75. <RouterLink v-if="column.component === 'RouterLink'" :to="getRouterLinkTo(scope.row, column)"
  76. class="custom-link">
  77. {{ scope.row[column.prop] }}
  78. </RouterLink>
  79. <div v-else-if="column.component === 'html'">
  80. <span v-html="htmlEvent(scope.row, column).html"></span>
  81. </div>
  82. <el-tag v-else-if="column.component === 'Tag'" :type="getTagTypeAndText(scope.row, column).type">
  83. {{ getTagTypeAndText(scope.row, column).text }}
  84. </el-tag>
  85. <ImagePreview v-else-if="column.component === 'Image'" :src="getImgTo(scope.row, column)" width="50px"
  86. height="50px"></ImagePreview>
  87. <span v-else>
  88. <div
  89. v-if="typeof getDisplayText(scope.row, column) === 'string' && getDisplayText(scope.row, column).includes('\n')"
  90. class="flex f-c ">
  91. <span v-for="(line, index) in getDisplayText(scope.row, column).split('\n')" :key="index">{{ line
  92. }}</span>
  93. </div>
  94. <span v-else>
  95. {{ getDisplayText(scope.row, column) }}
  96. </span>
  97. </span>
  98. </template>
  99. </el-table-column>
  100. <el-table-column label="操作" fixed="right" :width="props.columnwidth" v-if="props.showOperation" align="center">
  101. <template #default="scope">
  102. <div class="flex a-c j-c">
  103. <slot name="operation" :operationData="scope.row"></slot>
  104. </div>
  105. </template>
  106. </el-table-column>
  107. </el-table>
  108. <!-- </OverlayScrollbarsComponent> -->
  109. </div>
  110. <div class="page_box" v-if="props.pageInfo">
  111. <el-pagination v-model:current-page="props.pageInfo.pageNum" v-model:page-size="props.pageInfo.pageSize"
  112. :page-sizes="props.pageInfo.sizes" layout="total, sizes, prev, pager, next, jumper"
  113. :total="props.pageInfo.total" @size-change="handleSizeChange" @current-change="handleCurrentChange" />
  114. </div>
  115. </div>
  116. </template>
  117. <script lang="ts" setup>
  118. import { OverlayScrollbarsComponent } from 'overlayscrollbars-vue';
  119. import { defineProps, ref, onMounted, defineEmits } from "vue";
  120. import type { TableInstance } from "element-plus";
  121. interface TableRow {
  122. [key: string]: any;
  123. }
  124. const emit = defineEmits(['getList', 'selectAllChange', 'selectionChange', 'currentChange', 'handleMouseEnter', 'handleMouseLeave', 'changeShowTotal']);
  125. interface Props {
  126. tableData: Array<TableRow>;
  127. columnwidth: number,//操作栏宽度
  128. secondaryTable?: boolean,//是否展示二级表格
  129. highlightCurrentRow?: boolean,//是否可点击
  130. showOperation?: boolean,//操作显示/隐藏
  131. loading?: boolean,
  132. showSummary?: boolean, //合计
  133. columns: {
  134. prop: string;
  135. label: string;
  136. sortable?: boolean;
  137. width?: string;
  138. style?: string;
  139. align?: string,
  140. component?: string,
  141. formatter?: (row: any, column: any, cellValue: any, index: number) => any;
  142. }[];
  143. secondaryColumns?: {
  144. prop: string;
  145. label: string;
  146. sortable?: boolean;
  147. width?: string;
  148. style?: string;
  149. component?: string,
  150. formatter?: (row: any, column: any, cellValue: any, index: number) => any;
  151. }[];
  152. pageInfo?: {
  153. pageNum: number;
  154. pageSize: number;
  155. sizes: number[];
  156. total: number;
  157. };
  158. showSelect: boolean;
  159. showIndex: boolean;
  160. tableheight?: (number | string),
  161. maxHeight?: (number | string),
  162. uniqueIdName?: string,
  163. rowKey?: string,
  164. };
  165. interface Row {
  166. [key: string]: any; // 假设Row是一个具有任意属性的对象
  167. }
  168. interface RouterLink {
  169. name: string;
  170. }
  171. const props = withDefaults(defineProps<Props>(), {
  172. showOperation: true, // 默认值为 true
  173. });
  174. const loading = ref(false)
  175. const tableHeight = ref(props.tableheight); // 初始化表格高度
  176. const newSelectionList = ref<string[]>([]);
  177. const multipleTableRef = ref<TableInstance>();
  178. const TableData = ref<TableRow[]>();
  179. watch(props, () => {
  180. if (props.loading != undefined) {
  181. loading.value = props.loading;
  182. }
  183. })
  184. onMounted(() => {
  185. let counter = 1;
  186. TableData.value = props.tableData.map((row) => ({
  187. ...row,
  188. _edit: false, // 设置默认非编辑状态
  189. counter: counter++
  190. }));
  191. if (!props.tableheight) handleResize();
  192. });
  193. const filterMethod = (value: any, row: any, column: any) => {
  194. const list = props.tableData.filter(v => v[column.prop] === value)
  195. emit('changeShowTotal', list.length)
  196. return row[column.prop] === value
  197. }
  198. function handleResize() {
  199. // 获取当前窗口的高度
  200. const height = window.innerHeight;
  201. // 在这里可以执行其他操作,比如更新某个元素的高度等
  202. const tableElement = multipleTableRef.value?.$el as HTMLElement;
  203. if (tableElement && !props.showSummary) {
  204. const offsetTop = tableElement.offsetTop; // 获取 offsetTop
  205. tableHeight.value = height - offsetTop - 230 + "px";
  206. }
  207. }
  208. // 监听窗口的 resize 事件
  209. window.addEventListener('resize', handleResize);
  210. const selectable = () => true;
  211. const handleSizeChange = (val: number) => {
  212. if (props.pageInfo) {
  213. props.pageInfo.pageSize = val
  214. emit('getList', props.pageInfo);
  215. }
  216. }
  217. //多选
  218. const selectAllChange = (value: any[]) => {
  219. newSelectionList.value = [];
  220. if (value.length > 0) {
  221. value.map(val => {
  222. newSelectionList.value.push(val.id)
  223. })
  224. } else {
  225. newSelectionList.value = [];
  226. }
  227. emit('selectAllChange', newSelectionList.value)
  228. }
  229. /**
  230. * //单元格移入事件
  231. * @param row 行数据
  232. */
  233. const handleMouseEnter = (row: any) => {
  234. emit('handleMouseEnter', row)
  235. }
  236. /**
  237. * //单元格移出事件
  238. * @param row 行数据
  239. */
  240. const handleMouseLeave = (row: any) => {
  241. emit('handleMouseLeave', row)
  242. }
  243. //行点击事件
  244. const currentChange = (currentRow: any, _oldCurrentRow: any) => {
  245. emit('currentChange', currentRow)
  246. }
  247. const selectionChange = (value: any[]) => {
  248. newSelectionList.value = [];
  249. if (value.length > 0) {
  250. value.map(val => {
  251. if (props.uniqueIdName) {
  252. newSelectionList.value.push(val[props.uniqueIdName])
  253. } else {
  254. newSelectionList.value.push(val.id)
  255. }
  256. })
  257. } else {
  258. newSelectionList.value = [];
  259. }
  260. emit('selectionChange', newSelectionList.value)
  261. }
  262. const handleCurrentChange = (val: number) => {
  263. if (props.pageInfo) {
  264. props.pageInfo.pageNum = val
  265. emit('getList', props.pageInfo);
  266. }
  267. }
  268. //空值处理
  269. const displayValue = (value: any) => {
  270. return value === null || value === undefined || value === '' ? '-' : value;
  271. }
  272. // 格式化内容配置
  273. function getRouterLinkTo(row: Row, column: any): RouterLink {
  274. return column.formatter ? column.formatter(row, column, row[column.prop]) : { name: 'OrganizationAdd' };
  275. }
  276. function getImgTo(row: Row, column: any) {
  277. return row[column.prop];
  278. }
  279. function getTagTypeAndText(row: Row, column: any) {
  280. // 使用 formatter 返回一个对象,包含 type 和 text
  281. return column.formatter ? column.formatter(row, column, row[column.prop]) : { type: 'primary', text: row[column.prop] };
  282. }
  283. function getDisplayText(row: Row, column: any): string {
  284. return column.formatter ? column.formatter(row, column, row[column.prop]) : displayValue(row[column.prop]);
  285. }
  286. function htmlEvent(row: Row, column: any) {
  287. return column.formatter ? column.formatter(row, column, row[column.prop]) : row[column.prop]
  288. }
  289. </script>
  290. <style lang="scss" scoped>
  291. .el-table .el-table__cell .routerlick>a {
  292. color: red;
  293. }
  294. .containerTable {
  295. display: flex;
  296. flex-direction: column;
  297. width: 100%;
  298. height: auto;
  299. }
  300. .custom-link {
  301. color: #0083ff;
  302. /* 设置文字颜色 */
  303. text-decoration: none;
  304. /* 去掉下划线 */
  305. }
  306. .table_box {
  307. flex: 1;
  308. margin-bottom: 10px;
  309. overflow-y: auto;
  310. .table_item_container {
  311. padding: 0;
  312. margin-left: 20px;
  313. .table_item {
  314. margin: 5px 0;
  315. }
  316. }
  317. .table-title-btn {
  318. margin-bottom: 20px;
  319. :deep(.el-button) {
  320. margin-right: 20px;
  321. }
  322. }
  323. }
  324. .page_box {
  325. display: flex;
  326. align-items: center;
  327. justify-content: flex-end;
  328. }
  329. </style>