u-table2.vue 34 KB

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