No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 

1107 líneas
42 KiB

  1. <template>
  2. <view class="u-table2" :class="{ 'u-table-border': border }">
  3. <scroll-view scroll-x scroll-y class="u-table2-content"
  4. :style="{ height: height ? addUnit(height) : 'auto' }"
  5. @scroll="onScroll">
  6. <!-- 表头 -->
  7. <view v-if="showHeader" class="u-table-header"
  8. :class="{ 'u-table-sticky': fixedHeader }"
  9. :style="{minWidth: scrollWidth}">
  10. <view class="u-table-row">
  11. <view v-for="(col, colIndex) in columns" :key="col.key" class="u-table-cell"
  12. :class="[col.headerAlign ? 'u-text-' + col.headerAlign : (col.align ? 'u-text-' + col.align : '') ,
  13. headerCellClassName ? headerCellClassName(col, context) : '',
  14. getFixedClass(col)
  15. ]" :style="headerColStyle(col)" @click="handleHeaderClick(col)">
  16. <slot name="header" :column="col" :columnIndex="colIndex" :level="1" :context="context">
  17. </slot>
  18. <text v-if="!$slots['header']">{{ col.title }}</text>
  19. <template v-if="isColumnSortable(col)">
  20. <slot name="headerSort" :sortStatus="getSortValue(col.key)" :column="col"
  21. :columnIndex="colIndex" :level="1" :context="context">
  22. </slot>
  23. <view v-if="!$slots['headerSort']">
  24. {{ getSortIcon(col.key) }}
  25. </view>
  26. </template>
  27. </view>
  28. </view>
  29. </view>
  30. <!-- 表体 -->
  31. <view class="u-table-body" :style="{ minWidth: scrollWidth, maxHeight: maxHeight ? maxHeight + 'px' : 'none' }">
  32. <template v-if="data && data.length > 0">
  33. <!-- #ifdef MP-WEIXIN -->
  34. <template v-for="(item, flatIndex) in flattenedSortedData" :key="item.row[rowKey] || flatIndex">
  35. <view class="u-table-row u-table-row-child" :class="[highlightCurrentRow && currentRow === item.row ? 'u-table-row-highlight' : '',
  36. rowClassName ? rowClassName(item.row, item.rowIndex, context) : '',
  37. stripe && flatIndex % 2 === 1 ? 'u-table-row-zebra' : ''
  38. ]" :style="[{ height: rowHeight }, getRowStyle(item.row, item.rowIndex, item.level, item.parentRow)]" @click="handleRowClick(item.row)">
  39. <view v-for="(col, colIndex) in columns" :key="col.key" class="u-table-cell"
  40. :class="[col.align ? 'u-text-' + col.align : '',
  41. cellClassName ? cellClassName(item.row, col, context) : '',
  42. getFixedClass(col),
  43. isOverflowTooltipEnabled(col) ? 'u-table-cell-overflow' : '',
  44. getCellSpanClass(item.row, col, item.rowIndex, colIndex)
  45. ]"
  46. :style="[cellStyleInner({ row: item.row, column: col, rowIndex: item.rowIndex, columnIndex: colIndex, level: item.level }), getCellSpanStyle(item.row, col, item.rowIndex, colIndex)]">
  47. <view v-if="col.type === 'selection'">
  48. <checkbox :checked="isSelected(item.row)" @click.stop="toggleSelect(item.row)" />
  49. </view>
  50. <template v-else>
  51. <view v-if="col.key === computedMainCol && hasTree" @click.stop="toggleExpand(item.row, item.level)"
  52. :style="{ width: expandWidth }">
  53. <view v-if="hasExpandableChildren(item.row)">
  54. {{ isLazyLoading(item.row) ? '...' : (isExpanded(item.row) ? '▼' : '▶') }}
  55. </view>
  56. </view>
  57. <view class="u-table-cell_content"
  58. :class="{ 'u-table-cell_content-overflow': isOverflowTooltipEnabled(col) }">
  59. <slot name="cell" :row="item.row" :column="col" :prow="item.parentRow"
  60. :rowIndex="item.rowIndex" :columnIndex="colIndex" :level="item.level" :context="context">
  61. <text class="u-table-cell_text" :class="{ 'u-line-1': isOverflowTooltipEnabled(col) }" :lines="isOverflowTooltipEnabled(col) ? 1 : undefined">
  62. {{ item.row[col.key] }}
  63. </text>
  64. </slot>
  65. </view>
  66. </template>
  67. </view>
  68. </view>
  69. </template>
  70. <!-- #endif -->
  71. <!-- #ifndef MP-WEIXIN -->
  72. <table-row
  73. v-for="(row, rowIndex) in sortedData"
  74. :key="row[rowKey] || rowIndex"
  75. :row="row"
  76. :rowIndex="rowIndex"
  77. :parent-row="null"
  78. :columns="columns"
  79. :tree-props="treeProps"
  80. :row-key="rowKey"
  81. :expanded-keys="expandedKeys"
  82. :cell-style-inner="cellStyleInner"
  83. :is-expanded="isExpanded"
  84. :row-class-name="rowClassName"
  85. :row-style="rowStyle"
  86. :stripe="stripe"
  87. :cell-class-name="cellClassName"
  88. :get-fixed-class="getFixedClass"
  89. :context="context"
  90. :highlight-current-row="highlightCurrentRow"
  91. :current-row="currentRow"
  92. :handle-row-click="handleRowClick"
  93. :toggle-expand="toggleExpand"
  94. :level="1"
  95. :rowHeight="rowHeight"
  96. :hasTree="hasTree"
  97. :selectedRows="selectedRows"
  98. :expandWidth="expandWidth"
  99. :computedMainCol="computedMainCol"
  100. :span-method="spanMethod"
  101. :show-overflow-tooltip="showOverflowTooltip"
  102. :has-expandable-children="hasExpandableChildren"
  103. :is-lazy-loading="isLazyLoading"
  104. @toggle-select="toggleSelect"
  105. @row-click="handleRowClick"
  106. @toggle-expand="toggleExpand"
  107. >
  108. <template v-slot:cellChild="scope">
  109. <slot name="cell" :row="scope.row" :column="scope.column" :prow="scope.prow"
  110. :rowIndex="scope.rowIndex" :columnIndex="scope.columnIndex" :level="scope.level" :context="context">
  111. <text class="u-table-cell_text" :class="{ 'u-line-1': isOverflowTooltipEnabled(scope.column) }" :lines="isOverflowTooltipEnabled(scope.column) ? 1 : undefined">
  112. {{ scope.row[scope.column.key] }}
  113. </text>
  114. </slot>
  115. </template>
  116. </table-row>
  117. <!-- #endif -->
  118. </template>
  119. <template v-else>
  120. <slot name="empty">
  121. </slot>
  122. <view v-if="!$slots['empty']" class="u-table-empty">{{ emptyText }}</view>
  123. </template>
  124. </view>
  125. </scroll-view>
  126. <!-- 固定列浮动视图 -->
  127. <view v-if="showFixedColumnShadow" class="u-table-fixed-shadow" :style="{ height: tableHeight }">
  128. <!-- 表头 -->
  129. <view v-if="showHeader" class="u-table-header" :class="{ 'u-table-sticky': fixedHeader }" :style="{minWidth: scrollWidth}">
  130. <view class="u-table-row" :style="{height: headerHeight}">
  131. <view v-for="(col, colIndex) in visibleFixedLeftColumns" :key="col.key" class="u-table-cell"
  132. :style="headerColStyle(col)"
  133. :class="[col.align ? 'u-text-' + col.align : '',
  134. headerCellClassName ? headerCellClassName(col, context) : '',
  135. getFixedClass(col)
  136. ]" @click="handleHeaderClick(col)">
  137. <slot name="header" :column="col" :columnIndex="colIndex" :level="1" :context="context">
  138. </slot>
  139. <text v-if="!$slots['header']">{{ col.title }}</text>
  140. <template v-if="isColumnSortable(col)">
  141. <slot name="headerSort" :sortStatus="getSortValue(col.key)" :column="col"
  142. :columnIndex="colIndex" :level="1" :context="context">
  143. </slot>
  144. <view v-if="!$slots['headerSort']">
  145. {{ getSortIcon(col.key) }}
  146. </view>
  147. </template>
  148. </view>
  149. </view>
  150. </view>
  151. <!-- 表体 -->
  152. <view class="u-table-body" :style="{ minWidth: scrollWidth, maxHeight: maxHeight ? maxHeight + 'px' : 'none' }">
  153. <template v-if="data && data.length > 0">
  154. <!-- #ifdef MP-WEIXIN -->
  155. <template v-for="(item, flatIndex) in flattenedSortedData" :key="item.row[rowKey] || flatIndex">
  156. <view class="u-table-row u-table-row-child" :class="[highlightCurrentRow && currentRow === item.row ? 'u-table-row-highlight' : '',
  157. rowClassName ? rowClassName(item.row, item.rowIndex, context) : '',
  158. stripe && flatIndex % 2 === 1 ? 'u-table-row-zebra' : ''
  159. ]" :style="[{ height: rowHeight }, getRowStyle(item.row, item.rowIndex, item.level, item.parentRow)]" @click="handleRowClick(item.row)">
  160. <view v-for="(col, colIndex) in visibleFixedLeftColumns" :key="col.key" class="u-table-cell"
  161. :class="[col.align ? 'u-text-' + col.align : '',
  162. cellClassName ? cellClassName(item.row, col, context) : '',
  163. getFixedClass(col),
  164. isOverflowTooltipEnabled(col) ? 'u-table-cell-overflow' : '',
  165. getCellSpanClass(item.row, col, item.rowIndex, colIndex)
  166. ]"
  167. :style="[cellStyleInner({ row: item.row, column: col, rowIndex: item.rowIndex, columnIndex: colIndex, level: item.level }), getCellSpanStyle(item.row, col, item.rowIndex, colIndex)]">
  168. <view v-if="col.type === 'selection'">
  169. <checkbox :checked="isSelected(item.row)" @click.stop="toggleSelect(item.row)" />
  170. </view>
  171. <template v-else>
  172. <view v-if="col.key === computedMainCol && hasTree" @click.stop="toggleExpand(item.row, item.level)"
  173. :style="{ width: expandWidth }">
  174. <view v-if="hasExpandableChildren(item.row)">
  175. {{ isLazyLoading(item.row) ? '...' : (isExpanded(item.row) ? '▼' : '▶') }}
  176. </view>
  177. </view>
  178. <!-- 固定列浮动视图直接内联渲染,不使用 slot,避免与主表体 slot name="cell" 重名报错(微信小程序限制) -->
  179. <view class="u-table-cell_content"
  180. :class="{ 'u-table-cell_content-overflow': isOverflowTooltipEnabled(col) }">
  181. <text class="u-table-cell_text" :class="{ 'u-line-1': isOverflowTooltipEnabled(col) }" :lines="isOverflowTooltipEnabled(col) ? 1 : undefined">
  182. {{ item.row[col.key] }}
  183. </text>
  184. </view>
  185. </template>
  186. </view>
  187. </view>
  188. </template>
  189. <!-- #endif -->
  190. <!-- #ifndef MP-WEIXIN -->
  191. <template v-for="(row, rowIndex) in sortedData" :key="row[rowKey] || rowIndex">
  192. <!-- 子级渲染 (递归组件) -->
  193. <table-row
  194. :row="row"
  195. :rowIndex="rowIndex"
  196. :parent-row="null"
  197. :columns="visibleFixedLeftColumns"
  198. :tree-props="treeProps"
  199. :row-key="rowKey"
  200. :expanded-keys="expandedKeys"
  201. :cell-style-inner="cellStyleInner"
  202. :is-expanded="isExpanded"
  203. :row-class-name="rowClassName"
  204. :row-style="rowStyle"
  205. :stripe="stripe"
  206. :cell-class-name="cellClassName"
  207. :get-fixed-class="getFixedClass"
  208. :context="context"
  209. :highlight-current-row="highlightCurrentRow"
  210. :current-row="currentRow"
  211. :handle-row-click="handleRowClick"
  212. :toggle-expand="toggleExpand"
  213. :level="1"
  214. :rowHeight="rowHeight"
  215. :hasTree="hasTree"
  216. :selectedRows="selectedRows"
  217. :expandWidth="expandWidth"
  218. :computedMainCol="computedMainCol"
  219. :span-method="spanMethod"
  220. :show-overflow-tooltip="showOverflowTooltip"
  221. :has-expandable-children="hasExpandableChildren"
  222. :is-lazy-loading="isLazyLoading"
  223. @toggle-select="toggleSelect"
  224. @row-click="handleRowClick"
  225. @toggle-expand="toggleExpand"
  226. >
  227. <template v-slot:cellChild="scope">
  228. <slot name="cell" :row="scope.row" :column="scope.column" :prow="scope.prow"
  229. :rowIndex="scope.rowIndex" :columnIndex="scope.columnIndex" :level="scope.level" :context="context">
  230. <text class="u-table-cell_text" :class="{ 'u-line-1': isOverflowTooltipEnabled(scope.column) }" :lines="isOverflowTooltipEnabled(scope.column) ? 1 : undefined">
  231. {{ scope.row[scope.column.key] }}
  232. </text>
  233. </slot>
  234. </template>
  235. </table-row>
  236. </template>
  237. <!-- #endif -->
  238. </template>
  239. </view>
  240. </view>
  241. </view>
  242. </template>
  243. <script>
  244. import { addUnit, sleep } from '../../libs/function/index';
  245. import tableRow from './tableRow.vue'; // 引入递归组件
  246. export default {
  247. name: 'u-table2',
  248. components: {
  249. tableRow // 注册递归组件
  250. },
  251. props: {
  252. data: {
  253. type: Array,
  254. required: true,
  255. default: () => {
  256. return []
  257. }
  258. },
  259. columns: {
  260. type: Array,
  261. required: true,
  262. default: () => {
  263. return []
  264. },
  265. validator: cols =>
  266. cols.every(col =>
  267. ['default', 'selection', 'expand'].includes(col.type || 'default')
  268. )
  269. },
  270. stripe: {
  271. type: Boolean,
  272. default: false
  273. },
  274. border: {
  275. type: Boolean,
  276. default: false
  277. },
  278. height: {
  279. type: [String, Number],
  280. default: null
  281. },
  282. maxHeight: {
  283. type: [String, Number],
  284. default: null
  285. },
  286. showHeader: {
  287. type: Boolean,
  288. default: true
  289. },
  290. highlightCurrentRow: {
  291. type: Boolean,
  292. default: false
  293. },
  294. rowKey: {
  295. type: String,
  296. default: 'id'
  297. },
  298. currentRowKey: {
  299. type: [String, Number],
  300. default: null
  301. },
  302. rowStyle: {
  303. type: [Object, Function],
  304. default: () => ({})
  305. },
  306. cellClassName: {
  307. type: Function,
  308. default: null
  309. },
  310. cellStyle: {
  311. type: Function,
  312. default: null
  313. },
  314. headerCellClassName: {
  315. type: Function,
  316. default: null
  317. },
  318. rowClassName: {
  319. type: Function,
  320. default: null
  321. },
  322. context: {
  323. type: Object,
  324. default: null
  325. },
  326. showOverflowTooltip: {
  327. type: [Boolean, Object],
  328. default: false
  329. },
  330. lazy: {
  331. type: Boolean,
  332. default: false
  333. },
  334. load: {
  335. type: Function,
  336. default: null
  337. },
  338. treeProps: {
  339. type: Object,
  340. default: () => ({
  341. children: 'children',
  342. hasChildren: 'hasChildren'
  343. })
  344. },
  345. defaultExpandAll: {
  346. type: Boolean,
  347. default: false
  348. },
  349. expandRowKeys: {
  350. type: Array,
  351. default: () => []
  352. },
  353. sortOrders: {
  354. type: Array,
  355. default: () => ['ascending', 'descending']
  356. },
  357. sortable: {
  358. type: [Boolean, String],
  359. default: false
  360. },
  361. multiSort: {
  362. type: Boolean,
  363. default: false
  364. },
  365. sortBy: {
  366. type: String,
  367. default: null
  368. },
  369. sortMethod: {
  370. type: Function,
  371. default: null
  372. },
  373. filters: {
  374. type: Object,
  375. default: () => ({})
  376. },
  377. fixedHeader: {
  378. type: Boolean,
  379. default: true
  380. },
  381. emptyText: {
  382. type: String,
  383. default: '暂无数据'
  384. },
  385. // 添加mainCol属性,用于指定树形结构展开控制图标所在的列
  386. mainCol: {
  387. type: String,
  388. default: ''
  389. },
  390. expandWidth: {
  391. type: String,
  392. default: '25px'
  393. },
  394. rowHeight: {
  395. type: String,
  396. default: '36px'
  397. },
  398. // 添加spanMethod属性,用于合并单元格
  399. spanMethod: {
  400. type: Function,
  401. default: null
  402. }
  403. },
  404. emits: [
  405. 'select', 'select-all', 'selection-change',
  406. 'cell-click', 'row-click', 'row-dblclick',
  407. 'header-click', 'sort-change', 'filter-change',
  408. 'current-change', 'expand-change'
  409. ],
  410. data() {
  411. return {
  412. scrollWidth: 'auto',
  413. // 将setup中的ref转换为data属性
  414. expandedKeys: [...this.expandRowKeys],
  415. selectedRows: [],
  416. sortConditions: [],
  417. currentRow: null,
  418. scrollLeft: 0, // 新增滚动位置数据
  419. showFixedColumnShadow: false, // 是否显示固定列阴影
  420. fixedLeftColumns: [], // 左侧固定列
  421. tableHeight: 'auto', // 表格高度
  422. headerHeight: 'auto', // 新增表头高度属性
  423. hasTree: false, // 新增属性,用于判断是否存在树形结构
  424. lazyLoadingKeys: []
  425. }
  426. },
  427. mounted() {
  428. this.getComponentWidth()
  429. this.initDefaultExpandAll()
  430. // 处理currentRowKey初始化
  431. if (this.currentRowKey !== null) {
  432. const found = this.data.find(item => item[this.rowKey] === this.currentRowKey);
  433. if (found) {
  434. this.currentRow = found;
  435. }
  436. }
  437. // 获取固定列
  438. this.fixedLeftColumns = this.columns.filter(col => col.fixed === 'left');
  439. },
  440. computed: {
  441. // 将setup中的computed转换为computed属性
  442. filteredData() {
  443. return this.data.filter(row => {
  444. return Object.keys(this.filters).every(key => {
  445. const filter = this.filters[key];
  446. if (!filter) return true;
  447. return row[key]?.toString().includes(filter.toString());
  448. });
  449. });
  450. },
  451. sortedData() {
  452. if (!this.sortConditions.length) return this.filteredData;
  453. const data = [...this.filteredData];
  454. return data.sort((a, b) => {
  455. for (const condition of this.sortConditions) {
  456. const { field, order, column } = condition;
  457. const sortBy = column ? this.getColumnSortBy(column) : this.sortBy;
  458. let valA = this.getSortValueBy(a, field, sortBy);
  459. let valB = this.getSortValueBy(b, field, sortBy);
  460. if (this.sortMethod) {
  461. const result = this.sortMethod(a, b, field, this.context);
  462. if (result !== 0) return result * (order === 'ascending' ? 1 : -1);
  463. }
  464. if (valA < valB) return order === 'ascending' ? -1 : 1;
  465. if (valA > valB) return order === 'ascending' ? 1 : -1;
  466. }
  467. return 0;
  468. });
  469. },
  470. flattenedSortedData() {
  471. const result = [];
  472. const childrenKey = this.treeProps.children;
  473. const walk = (rows, parentRow = null, level = 1) => {
  474. if (!Array.isArray(rows) || rows.length === 0) return;
  475. rows.forEach((row, rowIndex) => {
  476. result.push({ row, parentRow, level, rowIndex });
  477. const children = row && row[childrenKey];
  478. if (children && children.length > 0 && this.isExpanded(row)) {
  479. walk(children, row, level + 1);
  480. }
  481. });
  482. };
  483. walk(this.sortedData);
  484. return result;
  485. },
  486. // 计算当前应该显示的固定左侧列
  487. visibleFixedLeftColumns() {
  488. if (this.scrollLeft <= 0) {
  489. return [];
  490. }
  491. let totalWidth = 0;
  492. let fixedWidth = 0;
  493. const visibleColumns = [];
  494. // 遍历所有列,不仅仅是固定列
  495. for (let i = 0; i < this.columns.length; i++) {
  496. const col = this.columns[i];
  497. const colWidth = col.width ? parseInt(col.width) : 100; // 默认宽度100px
  498. // 如果是固定列且滚动位置足够显示该列
  499. if (col.fixed === 'left' && this.scrollLeft > totalWidth - fixedWidth) {
  500. visibleColumns.push(col);
  501. fixedWidth += colWidth;
  502. }
  503. totalWidth += colWidth;
  504. }
  505. return visibleColumns;
  506. },
  507. // 获取mainCol的值,如果未设置则默认为第一列的key
  508. computedMainCol() {
  509. if (this.mainCol) {
  510. return this.mainCol;
  511. }
  512. // 修改为排除有type值的列
  513. const validColumns = this.columns.filter(col => !col.type);
  514. let mainCol = validColumns && validColumns.length > 0 ? validColumns[0].key : '';
  515. // console.log('mainCol', mainCol)
  516. return mainCol;
  517. }
  518. },
  519. watch: {
  520. // 将setup中的watch转换为watch属性
  521. expandRowKeys: {
  522. handler(newVal) {
  523. this.expandedKeys = [...newVal];
  524. this.initDefaultExpandAll();
  525. },
  526. immediate: true
  527. },
  528. currentRowKey: {
  529. handler(newVal) {
  530. const found = this.data.find(item => item[this.rowKey] === newVal);
  531. if (found) {
  532. this.currentRow = found;
  533. }
  534. },
  535. immediate: true
  536. },
  537. columns: {
  538. handler() {
  539. // this.fixedLeftColumns = this.columns.filter(col => col.fixed === 'left');
  540. },
  541. deep: true,
  542. immediate: false
  543. },
  544. data: {
  545. handler() {
  546. this.initDefaultExpandAll();
  547. },
  548. deep: true
  549. },
  550. defaultExpandAll() {
  551. this.initDefaultExpandAll();
  552. }
  553. },
  554. methods: {
  555. addUnit,
  556. isSelected(row) {
  557. return this.selectedRows.some(r => r[this.rowKey] === row[this.rowKey]);
  558. },
  559. getCellSpan(row, column, rowIndex, columnIndex) {
  560. if (typeof this.spanMethod !== 'function') {
  561. return { rowspan: 1, colspan: 1 };
  562. }
  563. const result = this.spanMethod({
  564. row,
  565. column,
  566. rowIndex,
  567. columnIndex,
  568. context: this.context
  569. });
  570. if (Array.isArray(result)) {
  571. const [rowspan, colspan] = result;
  572. return { rowspan: rowspan != null ? rowspan : 1, colspan: colspan != null ? colspan : 1 };
  573. }
  574. if (result && typeof result === 'object') {
  575. const { rowspan, colspan } = result;
  576. return { rowspan: rowspan != null ? rowspan : 1, colspan: colspan != null ? colspan : 1 };
  577. }
  578. return { rowspan: 1, colspan: 1 };
  579. },
  580. getCellSpanClass(row, column, rowIndex, columnIndex) {
  581. const span = this.getCellSpan(row, column, rowIndex, columnIndex);
  582. if (span.rowspan === 0 || span.colspan === 0) {
  583. return 'u-table-cell-hidden';
  584. }
  585. if (span.rowspan > 1 || span.colspan > 1) {
  586. return 'u-table-cell-merged';
  587. }
  588. return '';
  589. },
  590. getCellSpanStyle(row, column, rowIndex, columnIndex) {
  591. const span = this.getCellSpan(row, column, rowIndex, columnIndex);
  592. const style = {};
  593. if (span.rowspan > 1) {
  594. const currentHeight = parseInt(this.rowHeight);
  595. if (!isNaN(currentHeight)) {
  596. style.height = `${span.rowspan * currentHeight}px`;
  597. }
  598. }
  599. if (span.colspan > 1) {
  600. style.flex = span.colspan;
  601. }
  602. if (span.rowspan === 0 || span.colspan === 0) {
  603. style.display = 'none';
  604. }
  605. return style;
  606. },
  607. getRowStyle(row, rowIndex, level = 1, parentRow = null) {
  608. if (typeof this.rowStyle === 'function') {
  609. return this.rowStyle({ row, rowIndex, level, parentRow, context: this.context }) || {};
  610. }
  611. return this.rowStyle || {};
  612. },
  613. onScroll(e) {
  614. this.scrollLeft = e.detail.scrollLeft;
  615. // 获取所有左侧固定列
  616. this.fixedLeftColumns = this.columns.filter(col => col.fixed === 'left');
  617. // 计算是否需要显示固定列阴影
  618. if (this.fixedLeftColumns.length > 0) {
  619. this.showFixedColumnShadow = this.scrollLeft > 0;
  620. }
  621. },
  622. getFixedShadowStyle(col, index) {
  623. let style = {
  624. width: col.width ? addUnit(col.width) : 'auto',
  625. };
  626. if (col?.style) {
  627. style = {...style, ...col?.style};
  628. }
  629. return style;
  630. },
  631. getFixedClass(col) {
  632. return ''; // 不再使用原来的固定列样式类
  633. },
  634. isOverflowTooltipEnabled(column) {
  635. if (column && column.type === 'selection') {
  636. return false;
  637. }
  638. if (column && typeof column.showOverflowTooltip !== 'undefined') {
  639. return !!column.showOverflowTooltip;
  640. }
  641. return !!this.showOverflowTooltip;
  642. },
  643. isColumnSortable(column) {
  644. return typeof column.sortable !== 'undefined' ? !!column.sortable : !!this.sortable;
  645. },
  646. getColumnSortBy(column) {
  647. if (typeof column.sortBy !== 'undefined') {
  648. return column.sortBy;
  649. }
  650. return this.sortBy;
  651. },
  652. getColumnSortOrders(column) {
  653. if (Array.isArray(column.sortOrders) && column.sortOrders.length > 0) {
  654. return column.sortOrders;
  655. }
  656. return this.sortOrders;
  657. },
  658. getSortValueBy(row, field, sortBy) {
  659. if (typeof sortBy === 'function') {
  660. return sortBy(row);
  661. }
  662. if (Array.isArray(sortBy) && sortBy.length > 0) {
  663. return sortBy.map(key => row[key]).join('');
  664. }
  665. if (typeof sortBy === 'string' && sortBy) {
  666. return row[sortBy];
  667. }
  668. return row[field];
  669. },
  670. hasExpandableChildren(row) {
  671. if (!row) {
  672. return false;
  673. }
  674. const children = row[this.treeProps.children];
  675. return (Array.isArray(children) && children.length > 0) || !!row[this.treeProps.hasChildren];
  676. },
  677. isLazyLoading(row) {
  678. return this.lazyLoadingKeys.includes(row[this.rowKey]);
  679. },
  680. initDefaultExpandAll() {
  681. if (!this.defaultExpandAll) {
  682. return;
  683. }
  684. const keys = [];
  685. const childrenKey = this.treeProps.children;
  686. const walk = (rows) => {
  687. if (!Array.isArray(rows)) {
  688. return;
  689. }
  690. rows.forEach(row => {
  691. if (!row) {
  692. return;
  693. }
  694. if (this.hasExpandableChildren(row)) {
  695. keys.push(row[this.rowKey]);
  696. }
  697. walk(row[childrenKey]);
  698. });
  699. };
  700. walk(this.data);
  701. this.expandedKeys = Array.from(new Set([...this.expandedKeys, ...keys]));
  702. },
  703. headerColStyle(col) {
  704. let style = {
  705. width: col.width ? addUnit(col.width) : 'auto',
  706. flex: col.width ? 'none' : 1
  707. };
  708. if (col?.style) {
  709. style = {...style, ...col?.style};
  710. }
  711. return style;
  712. },
  713. setCellStyle(e) {
  714. this.cellStyle = e
  715. },
  716. cellStyleInner(scope) {
  717. let style = {
  718. width: scope.column?.width ? addUnit(scope.column.width) : 'auto',
  719. flex: scope.column?.width ? 'none' : 1
  720. };
  721. // 只有展开列设置padding
  722. if (scope.column.key == this.computedMainCol) {
  723. style.paddingLeft = (16 * (scope.level -1 )) + 2 + 'px'
  724. }
  725. if (this.cellStyle != null) {
  726. let styleCalc = this.cellStyle({...scope, context: this.context})
  727. if (styleCalc != null) {
  728. style = {...style, ...styleCalc}
  729. }
  730. }
  731. return style;
  732. },
  733. // 获取组件的宽度
  734. async getComponentWidth() {
  735. // 延时一定时间,以获取dom尺寸
  736. await sleep(30)
  737. this.$uGetRect('.u-table-row').then(size => {
  738. this.scrollWidth = size.width + 'px'
  739. })
  740. // 获取表头高度并设置
  741. this.$uGetRect('.u-table-header').then(size => {
  742. if (size.height) {
  743. this.headerHeight = size.height + 'px';
  744. }
  745. })
  746. // 遍历数据列表第一层判断是否存在树形结构
  747. this.hasTree = this.sortedData.some(item => {
  748. return item[this.treeProps.children] && item[this.treeProps.children].length > 0;
  749. });
  750. },
  751. // 将setup中的函数转换为methods
  752. handleRowClick(row) {
  753. if (this.highlightCurrentRow) {
  754. const oldRow = this.currentRow;
  755. this.currentRow = row;
  756. this.$emit('current-change', row, oldRow);
  757. }
  758. this.$emit('row-click', row);
  759. },
  760. handleHeaderClick(column) {
  761. if (!this.isColumnSortable(column)) return;
  762. const index = this.sortConditions.findIndex(c => c.field === column.key);
  763. const sortOrders = this.getColumnSortOrders(column).filter(Boolean);
  764. let newOrder = sortOrders[0] || 'ascending';
  765. if (index >= 0) {
  766. const currentOrder = this.sortConditions[index].order;
  767. const nextIndex = sortOrders.indexOf(currentOrder) + 1;
  768. if (nextIndex > 0 && nextIndex < sortOrders.length) {
  769. newOrder = sortOrders[nextIndex];
  770. } else {
  771. this.sortConditions.splice(index, 1);
  772. this.$emit('sort-change', this.sortConditions);
  773. return;
  774. }
  775. }
  776. if (!this.multiSort) {
  777. this.sortConditions = [{ field: column.key, order: newOrder, column }];
  778. } else {
  779. if (index >= 0) {
  780. this.sortConditions[index].order = newOrder;
  781. this.sortConditions[index].column = column;
  782. } else {
  783. this.sortConditions.push({ field: column.key, order: newOrder, column });
  784. }
  785. }
  786. this.$emit('sort-change', this.sortConditions);
  787. },
  788. getSortIcon(field) {
  789. const cond = this.sortConditions.find(c => c.field === field);
  790. if (!cond) return '';
  791. return cond.order === 'ascending' ? '↑' : '↓';
  792. },
  793. getSortValue(field) {
  794. const cond = this.sortConditions.find(c => c.field === field);
  795. if (!cond) return '';
  796. return cond.order === 'ascending';
  797. },
  798. toggleSelect(row) {
  799. const index = this.selectedRows.findIndex(r => r[this.rowKey] === row[this.rowKey]);
  800. if (index >= 0) {
  801. // 取消选中当前行及其所有子节点
  802. this.selectedRows.splice(index, 1);
  803. // 递归取消所有子节点
  804. this.unselectChildren(row);
  805. } else {
  806. // 选中当前行及其所有子节点
  807. this.selectedRows.push(row);
  808. // 递归选中所有子节点
  809. this.selectChildren(row);
  810. }
  811. console.log(this.selectedRows)
  812. this.$emit('selection-change', this.selectedRows);
  813. this.$emit('select', row);
  814. },
  815. toggleExpand(row, level = 1) {
  816. // console.log(row)
  817. const key = row[this.rowKey];
  818. const index = this.expandedKeys.indexOf(key);
  819. if (index === -1) {
  820. this.expandedKeys.push(key);
  821. this.loadLazyChildren(row, level);
  822. } else {
  823. this.expandedKeys.splice(index, 1);
  824. }
  825. this.$emit('expand-change', this.expandedKeys);
  826. },
  827. loadLazyChildren(row, level = 1) {
  828. if (!this.lazy || typeof this.load !== 'function') {
  829. return;
  830. }
  831. const childrenKey = this.treeProps.children;
  832. const key = row[this.rowKey];
  833. const children = row[childrenKey];
  834. if ((Array.isArray(children) && children.length > 0) || this.lazyLoadingKeys.includes(key)) {
  835. return;
  836. }
  837. this.lazyLoadingKeys.push(key);
  838. const resolve = (childrenList = []) => {
  839. row[childrenKey] = Array.isArray(childrenList) ? childrenList : [];
  840. this.lazyLoadingKeys = this.lazyLoadingKeys.filter(item => item !== key);
  841. this.getComponentWidth();
  842. };
  843. const result = this.load(row, {
  844. row,
  845. level,
  846. expanded: this.isExpanded(row),
  847. loading: true,
  848. context: this.context
  849. }, resolve);
  850. if (result && typeof result.then === 'function') {
  851. result.then(resolve).catch(() => {
  852. this.lazyLoadingKeys = this.lazyLoadingKeys.filter(item => item !== key);
  853. });
  854. }
  855. },
  856. isExpanded(row) {
  857. if (!row) {
  858. return false;
  859. }
  860. return this.expandedKeys.includes(row[this.rowKey]);
  861. },
  862. // 新增方法:递归选中所有子节点
  863. selectChildren(row) {
  864. const children = row[this.treeProps.children];
  865. if (children && children.length > 0) {
  866. children.forEach(child => {
  867. // 检查是否已选中,避免重复添加
  868. const childIndex = this.selectedRows.findIndex(r => r[this.rowKey] === child[this.rowKey]);
  869. if (childIndex === -1) {
  870. this.selectedRows.push(child);
  871. }
  872. // 递归处理子节点的子节点
  873. this.selectChildren(child);
  874. });
  875. }
  876. },
  877. // 新增方法:递归取消选中所有子节点
  878. unselectChildren(row) {
  879. const children = row[this.treeProps.children];
  880. if (children && children.length > 0) {
  881. children.forEach(child => {
  882. const childIndex = this.selectedRows.findIndex(r => r[this.rowKey] === child[this.rowKey]);
  883. if (childIndex >= 0) {
  884. this.selectedRows.splice(childIndex, 1);
  885. }
  886. // 递归处理子节点的子节点
  887. this.unselectChildren(child);
  888. });
  889. }
  890. },
  891. }
  892. };
  893. </script>
  894. <style lang="scss" scoped>
  895. .u-table2 {
  896. width: auto;
  897. overflow: auto;
  898. white-space: nowrap;
  899. position: relative;
  900. color: var(--up-content-color, #606266);
  901. background-color: var(--up-card-bg-color, #ffffff);
  902. .u-table-header {
  903. min-width: 100% !important;
  904. width: fit-content;
  905. background-color: var(--up-table2-header-bg-color, var(--up-bg-color, #f5f7fa));
  906. color: var(--up-main-color, #303133);
  907. }
  908. .u-table-body {
  909. min-width: 100% !important;
  910. width: fit-content;
  911. position: relative;
  912. background-color: var(--up-card-bg-color, #ffffff);
  913. }
  914. .u-table-sticky {
  915. position: sticky;
  916. top: 0;
  917. z-index: 10;
  918. }
  919. .u-table-row {
  920. display: flex;
  921. flex-direction: row;
  922. overflow: hidden;
  923. position: relative;
  924. // min-height: 40px;
  925. }
  926. // 添加border样式支持
  927. &.u-table-border {
  928. border-top: 1px solid var(--up-border-color, #ebeef5);
  929. border-left: 1px solid var(--up-border-color, #ebeef5);
  930. border-right: 1px solid var(--up-border-color, #ebeef5);
  931. .u-table-cell {
  932. border-right: 1px solid var(--up-border-color, #ebeef5);
  933. }
  934. .u-table-cell:last-child {
  935. border-right: none;
  936. }
  937. }
  938. .u-table-cell {
  939. flex: 1;
  940. display: flex;
  941. flex-direction: row;
  942. align-items: center;
  943. min-width: 0;
  944. box-sizing: border-box;
  945. padding: 10px 1px;
  946. font-size: 14px;
  947. white-space: nowrap;
  948. overflow: hidden;
  949. text-overflow: ellipsis;
  950. line-height: 1.1;
  951. color: var(--up-content-color, #606266);
  952. border-bottom: 1px solid var(--up-border-color, #ebeef5);
  953. &.u-text-left {
  954. justify-content: flex-start;
  955. text-align: left;
  956. }
  957. &.u-text-center {
  958. justify-content: center;
  959. text-align: center;
  960. }
  961. &.u-text-right {
  962. justify-content: flex-end;
  963. text-align: right;
  964. }
  965. }
  966. .u-table-cell_content {
  967. width: 100%;
  968. min-width: 0;
  969. max-width: 100%;
  970. flex: 1;
  971. box-sizing: border-box;
  972. display: block;
  973. text-align: inherit;
  974. }
  975. .u-table-cell_text {
  976. width: 100%;
  977. min-width: 0;
  978. max-width: 100%;
  979. display: block;
  980. box-sizing: border-box;
  981. text-align: inherit;
  982. }
  983. .u-table-cell_content-overflow,
  984. .u-table-cell_content-overflow .u-table-cell_text {
  985. width: 100%;
  986. min-width: 0;
  987. max-width: 100%;
  988. display: block;
  989. overflow: hidden;
  990. text-overflow: ellipsis;
  991. white-space: nowrap;
  992. }
  993. .u-table-row-zebra {
  994. background-color: var(--up-table2-zebra-bg-color, rgba(255, 255, 255, 0.03));
  995. }
  996. .u-table-row-highlight {
  997. background-color: var(--up-table2-highlight-bg-color, var(--up-bg-color, #f5f7fa));
  998. }
  999. .u-table-empty {
  1000. text-align: center;
  1001. padding: 20px;
  1002. color: var(--up-tips-color, #999);
  1003. }
  1004. .u-table-cell-hidden {
  1005. opacity: 0;
  1006. }
  1007. .u-table-cell-merged {
  1008. z-index: 1;
  1009. }
  1010. }
  1011. // 固定列浮动视图
  1012. .u-table-fixed-shadow {
  1013. position: absolute;
  1014. top: 0;
  1015. left: 0;
  1016. width: auto;
  1017. z-index: 20;
  1018. box-shadow: 2px 0 5px rgba(0, 0, 0, 0.15);
  1019. overflow: hidden;
  1020. background-color: var(--up-card-bg-color, #ffffff);
  1021. color: var(--up-content-color, #606266);
  1022. }
  1023. // .u-table-fixed-row {
  1024. // display: flex;
  1025. // flex-direction: row;
  1026. // align-items: center;
  1027. // border-bottom: 1rpx solid #ebeef5;
  1028. // position: relative;
  1029. // }
  1030. // 为固定列也添加border样式支持
  1031. .u-table-fixed-shadow .u-table-border {
  1032. .u-table-cell {
  1033. border-right: 1rpx solid var(--up-border-color, #ebeef5);
  1034. }
  1035. .u-table-cell:last-child {
  1036. border-right: none;
  1037. }
  1038. }
  1039. </style>