u-table.vue 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. <template>
  2. <u-scroll-list :indicator="false" :scroll="scrollX">
  3. <view class="u-table" :class="[customClass, border ? 'is-border' : '']" :style="tableStyle">
  4. <!-- 表头 -->
  5. <view v-if="showHeader" class="u-table__header" ref="u-table__header">
  6. <block v-for="(column, index) in childrenData" :key="index">
  7. <view v-if="column.show" class="u-table__header__column" :style="[cellStyles(column)]"
  8. @click="handleHeaderClick(column, index)">
  9. <view class="u-table__cell" :class="[cellClasses(column, index)]"
  10. :style="{ minHeight: $u.addUnit(rowHeight) }">
  11. <!-- 多选表头复选框 -->
  12. <template v-if="column.type === 'selection'">
  13. <u-checkbox :checked="isAllSelected" :indeterminate="isIndeterminate"
  14. @change="handleSelectAll"></u-checkbox>
  15. </template>
  16. <!-- 普通表头 -->
  17. <template v-else>
  18. <text class="u-table__value" :class="[{'is-ellipsis': column.headerEllipsis }]">{{ column.label }}</text>
  19. <view v-if="column.sortable && column.type != 'index'" class="u-table__sort-icon">
  20. <view class="u-table__sort-arrow u-table__sort-arrow--up"
  21. :class="{ 'is-active': sortColumn === column.prop && sortOrder === 'asc' }">
  22. </view>
  23. <view class="u-table__sort-arrow u-table__sort-arrow--down"
  24. :class="{ 'is-active': sortColumn === column.prop && sortOrder === 'desc' }">
  25. </view>
  26. </view>
  27. </template>
  28. </view>
  29. </view>
  30. </block>
  31. </view>
  32. <!-- 表体 -->
  33. <scroll-view class="u-table__body" :style="bodyStyle" :scroll-y="true">
  34. <!-- 数据行容器 -->
  35. <view class="u-table__rows">
  36. <slot></slot>
  37. </view>
  38. <!-- 空数据提示 -->
  39. <view v-if="!data || data.length === 0" class="u-table__empty"
  40. :style="{ height: $u.addUnit(emptyHeight) }">
  41. <slot name="empty">
  42. <view class="u-table__empty-content">
  43. <text class="u-table__empty-text">{{ emptyText }}</text>
  44. </view>
  45. </slot>
  46. </view>
  47. </scroll-view>
  48. <!-- 表尾合计行 -->
  49. <view v-if="showSummary && data && data.length > 0" class="u-table__footer" ref="u-table__footer">
  50. <block v-for="(column, index) in children" :key="index">
  51. <view v-if="column.show" class="u-table__footer__column" :style="[cellStyles(column)]">
  52. <view class="u-table__cell u-table__summary-cell" :class="[cellClasses(column, index)]"
  53. :style="{ height: $u.addUnit(rowHeight) }">
  54. <text class="u-table__value">{{ getSummaryValue(column, index) }}</text>
  55. </view>
  56. </view>
  57. </block>
  58. </view>
  59. </view>
  60. </u-scroll-list>
  61. </template>
  62. <script>
  63. // #ifdef APP-NVUE
  64. const dom = uni.requireNativePlugin('dom')
  65. // #endif
  66. import props from './props.js';
  67. import mixin from '../../libs/mixin/mixin'
  68. import mpMixin from '../../libs/mixin/mpMixin'
  69. /**
  70. * table 表格
  71. * @description 用于展示多条结构类似的数据,可对数据进行排序等操作
  72. * @tutorial https://www.uviewui.com/components/table.html
  73. * @property {Array} data 显示的数据 (默认 [] )
  74. * @property {Boolean} border 是否带有边框 (默认 true )
  75. * @property {Boolean} scrollX 是否允许横向滚动 (默认 true )
  76. * @property {String|Number} round 设置圆角值 (默认 0 )
  77. * @property {Boolean} stripe 是否为斑马纹表 (默认 true )
  78. * @property {String|Number} height Table 的高度 (默认 null )
  79. * @property {String|Number} rowHeight 行高 (默认 50 )
  80. * @property {Boolean} showHeader 是否显示表头 (默认 true )
  81. * @property {Object} headerCellStyle 表头单元格样式 (默认 {} )
  82. * @property {Boolean} ellipsis 是否超出2行隐藏 (默认 true )
  83. * @property {String} emptyText 空数据时显示的文本 (默认 '暂无数据' )
  84. * @property {String|Number} emptyHeight 空数据区域高度 (默认 200 )
  85. * @property {String} rowKey 行数据的Key,用于优化Table的渲染 (默认 'id' )
  86. * @property {Array} defaultSelection 默认选中的行 (默认 [] )
  87. * @property {Boolean} showSummary 是否显示表尾合计行 (默认 false )
  88. * @property {String} sumText 表尾合计行第一列的文本 (默认 '合计' )
  89. * @property {Function} summaryMethod 自定义的合计计算方法 (默认 null )
  90. * @property {Array} summaryColumns 需要合计的列(prop数组) (默认 [] )
  91. * @property {Object} customStyle 定义需要用到的外部样式
  92. * @event {Function} sort-change 排序变化事件
  93. * @event {Function} row-click 行点击事件
  94. * @event {Function} cell-click 单元格点击事件
  95. * @event {Function} selection-change 选择项发生变化时会触发该事件
  96. * @event {Function} select 用户手动勾选数据行的 Checkbox 时触发的事件
  97. * @event {Function} select-all 用户手动勾选全选 Checkbox 时触发的事件
  98. * @example <u-table :data="dataList" :columns="columns"></u-table>
  99. */
  100. export default {
  101. name: 'u-table',
  102. mixins: [mpMixin, mixin, props],
  103. data() {
  104. return {
  105. children: [],
  106. childrenData: [],
  107. sortColumn: null,
  108. sortOrder: null,
  109. headerList: [],
  110. headerHeight: 0,
  111. footerHeight: 0,
  112. // 多选相关状态
  113. selectedRows: [], // 当前选中的行数据
  114. selectedRowKeys: [], // 当前选中的行Key
  115. expandedRowKeys: [] // 当前展开的行Key
  116. }
  117. },
  118. computed: {
  119. parentData() {
  120. return [
  121. this.data,
  122. this.border,
  123. this.stripe,
  124. this.rowHeight,
  125. this.ellipsis,
  126. this.cellStyle,
  127. this.selectedRowKeys,
  128. this.toggleRowSelection,
  129. this.mergeInfo
  130. ]
  131. },
  132. // 排序后的数据
  133. sortedData() {
  134. if (!this.sortColumn || !this.sortOrder) {
  135. return this.data || []
  136. }
  137. const dataToSort = [...(this.data || [])]
  138. return dataToSort.sort((a, b) => {
  139. const aVal = this.getValueByPath(a, this.sortColumn)
  140. const bVal = this.getValueByPath(b, this.sortColumn)
  141. // 处理不同数据类型的比较
  142. let result = 0
  143. // 数字比较
  144. if (typeof aVal === 'number' && typeof bVal === 'number') {
  145. result = aVal - bVal
  146. }
  147. // 字符串比较
  148. else if (typeof aVal === 'string' && typeof bVal === 'string') {
  149. result = aVal.localeCompare(bVal)
  150. }
  151. // 日期比较
  152. else if (aVal instanceof Date && bVal instanceof Date) {
  153. result = aVal.getTime() - bVal.getTime()
  154. }
  155. // 其他类型转换为字符串比较
  156. else {
  157. const aStr = String(aVal || '')
  158. const bStr = String(bVal || '')
  159. result = aStr.localeCompare(bStr)
  160. }
  161. return this.sortOrder === 'asc' ? result : -result
  162. })
  163. },
  164. // 合并单元格信息
  165. mergeInfo() {
  166. if (!this.spanMethod && (!this.mergeConfig || this.mergeConfig.length === 0)) {
  167. return {}
  168. }
  169. const info = {}
  170. const data = this.sortedData
  171. // 使用自定义合并方法
  172. if (this.spanMethod && uni.$u.test.func(this.spanMethod)) {
  173. data.forEach((row, rowIndex) => {
  174. this.childrenData.forEach((column, columnIndex) => {
  175. if (!column.prop) return
  176. const spanResult = this.spanMethod({
  177. row,
  178. column: column,
  179. rowIndex,
  180. columnIndex
  181. })
  182. if (spanResult && (spanResult.rowspan !== 1 || spanResult.colspan !== 1)) {
  183. const key = `${rowIndex}-${column.prop}`
  184. info[key] = {
  185. rowspan: spanResult.rowspan || 1,
  186. colspan: spanResult.colspan || 1,
  187. display: spanResult.rowspan === 0 || spanResult.colspan === 0 ? 'none' : 'block'
  188. }
  189. }
  190. })
  191. })
  192. }
  193. // 使用简化配置
  194. else if (this.mergeConfig && this.mergeConfig.length > 0) {
  195. this.mergeConfig.forEach(config => {
  196. if (!config.prop || !config.ranges) return
  197. config.ranges.forEach(range => {
  198. if (range.startRow !== undefined && range.endRow !== undefined) {
  199. const rowspan = range.endRow - range.startRow + 1
  200. // 第一行显示合并的单元格
  201. const firstRowKey = `${range.startRow}-${config.prop}`
  202. info[firstRowKey] = {
  203. rowspan: rowspan,
  204. colspan: range.colspan || 1,
  205. display: 'block'
  206. }
  207. // 其他行隐藏
  208. for (let i = range.startRow + 1; i <= range.endRow; i++) {
  209. const key = `${i}-${config.prop}`
  210. info[key] = {
  211. rowspan: 0,
  212. colspan: 0,
  213. display: 'none'
  214. }
  215. }
  216. }
  217. })
  218. })
  219. }
  220. return info
  221. },
  222. // 是否全选
  223. isAllSelected() {
  224. return this.data.length > 0 && this.selectedRowKeys.length === this.data.length
  225. },
  226. // 是否半选状态
  227. isIndeterminate() {
  228. return this.selectedRowKeys.length > 0 && this.selectedRowKeys.length < this.data.length
  229. },
  230. tableStyle() {
  231. const style = {}
  232. if (this.height) {
  233. style.height = this.$u.addUnit(this.height)
  234. style.display = 'flex'
  235. style.flexDirection = 'column'
  236. }
  237. if (this.round) {
  238. style.borderRadius = this.$u.addUnit(this.round)
  239. }
  240. return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));
  241. },
  242. bodyStyle() {
  243. const style = {}
  244. if (this.height) {
  245. if (this.showHeader || this.showSummary) {
  246. // 表体高度 = 总高度 - 表头高度 - 表尾高度
  247. const totalHeight = parseInt(this.height) || 0
  248. let usedHeight = 0
  249. if (this.showHeader) usedHeight += this.headerHeight
  250. if (this.showSummary && this.data && this.data.length > 0) usedHeight += this.footerHeight
  251. if (totalHeight > usedHeight) {
  252. style.height = this.$u.addUnit(totalHeight - usedHeight)
  253. }
  254. } else {
  255. style.height = this.$u.addUnit(this.height)
  256. }
  257. style.flex = 1
  258. }
  259. return this.$u.addStyle(style)
  260. },
  261. cellClasses(column, index) {
  262. return (column, index) => {
  263. let classes = [
  264. `is-${column.headerAlign || 'left'}`,
  265. ];
  266. if (this.border) {
  267. classes.push('is-border');
  268. }
  269. // #ifdef MP-ALIPAY || MP-TOUTIAO
  270. classes = classes.join(' ')
  271. // #endif
  272. return classes;
  273. }
  274. },
  275. cellStyles(column) {
  276. return (column) => {
  277. const style = {}
  278. if (column.width) {
  279. style.width = this.$u.addUnit(column.width)
  280. } else if (column.minWidth) {
  281. style.minWidth = this.$u.addUnit(column.minWidth)
  282. } else {
  283. style.flex = 1;
  284. }
  285. return uni.$u.deepMerge(style, uni.$u.addStyle(this.headerCellStyle));
  286. }
  287. }
  288. },
  289. created() {
  290. //this.children = []
  291. // 初始化默认选中项
  292. this.initDefaultSelection()
  293. },
  294. mounted() {
  295. this.getHeaderHeight()
  296. this.getFooterHeight()
  297. },
  298. watch: {
  299. // 监听排序状态变化,更新子组件数据
  300. sortedData: {
  301. handler() {
  302. this.updateChildrenData()
  303. },
  304. immediate: true
  305. },
  306. // 监听原始数据变化
  307. data: {
  308. handler() {
  309. this.updateChildrenData()
  310. // 数据变化时重新检查选中状态
  311. this.validateSelection()
  312. },
  313. immediate: true
  314. },
  315. // 监听默认选中项变化
  316. defaultSelection: {
  317. handler() {
  318. this.initDefaultSelection()
  319. },
  320. immediate: true
  321. },
  322. // 监听showSummary变化,重新计算布局
  323. showSummary: {
  324. handler() {
  325. this.$nextTick(() => {
  326. this.getFooterHeight()
  327. })
  328. }
  329. },
  330. // 监听parentData变化
  331. parentData: {
  332. handler() {
  333. if (this.children.length) {
  334. this.children.map(child => {
  335. child.init()
  336. })
  337. }
  338. }
  339. },
  340. // 监听children变化,兼容vue2微信小程序
  341. children: {
  342. // #ifdef VUE3
  343. deep: true, // 兼容微信小程序vue3写法
  344. // #endif
  345. handler(newval) {
  346. if (newval && newval.length > 0) {
  347. this.childrenData = newval.map(item => {
  348. return {
  349. show: item.show,
  350. label: item.label,
  351. prop: item.prop,
  352. headerAlign: item.headerAlign,
  353. headerEllipsis: item.headerEllipsis,
  354. align: item.align,
  355. width: item.width,
  356. minWidth: item.minWidth,
  357. sortable: item.sortable,
  358. type: item.type,
  359. fixed: item.fixed,
  360. columnKey: item.columnKey,
  361. className: item.className,
  362. customStyle: item.customStyle,
  363. formatter: item.formatter,
  364. }
  365. })
  366. }
  367. }
  368. }
  369. },
  370. // #ifdef VUE3
  371. emits: ['cellClick', 'sortChange', 'selectionChange', 'select', 'selectAll'],
  372. // #endif
  373. methods: {
  374. // 获取合计行的值
  375. getSummaryValue(column, index) {
  376. // 如果使用自定义合计方法
  377. if (this.summaryMethod && uni.$u.test.func(this.summaryMethod)) {
  378. const summaryData = this.summaryMethod({
  379. columns: this.childrenData,
  380. data: this.sortedData,
  381. column,
  382. index
  383. })
  384. if (Array.isArray(summaryData)) {
  385. return summaryData[index] || ''
  386. } else if (typeof summaryData === 'object' && summaryData[column.prop]) {
  387. return summaryData[column.prop]
  388. }
  389. }
  390. // 默认合计逻辑
  391. if (index === 0) {
  392. // 第一列显示合计文本
  393. return this.sumText
  394. }
  395. // 检查该列是否需要合计
  396. if (column.prop && this.summaryColumns.includes(column.prop)) {
  397. return this.calculateColumnSum(column.prop)
  398. }
  399. // 如果summaryColumns为空数组,则自动合计数字列
  400. if (this.summaryColumns.length === 0 && column.prop && column.type !== 'selection' && column.type !== 'index') {
  401. const isNumericColumn = this.sortedData.every(row => {
  402. const value = this.getValueByPath(row, column.prop)
  403. return value === null || value === undefined || value === '' || !isNaN(Number(value))
  404. })
  405. if (isNumericColumn) {
  406. return this.calculateColumnSum(column.prop)
  407. }
  408. }
  409. return ''
  410. },
  411. // 计算列的合计值
  412. calculateColumnSum(prop) {
  413. if (!prop || !this.sortedData || this.sortedData.length === 0) {
  414. return '0'
  415. }
  416. let sum = 0
  417. let hasValidNumber = false
  418. this.sortedData.forEach(row => {
  419. const value = this.getValueByPath(row, prop)
  420. const numValue = parseFloat(value)
  421. if (!isNaN(numValue)) {
  422. sum += numValue
  423. hasValidNumber = true
  424. }
  425. })
  426. if (!hasValidNumber) {
  427. return ''
  428. }
  429. // 保留两位小数并去除多余的0
  430. return parseFloat(sum.toFixed(2)).toString()
  431. },
  432. // 初始化默认选中项
  433. initDefaultSelection() {
  434. if (this.defaultSelection && this.defaultSelection.length > 0) {
  435. this.selectedRows = [...this.defaultSelection]
  436. this.selectedRowKeys = this.defaultSelection.map(row => this.getRowKey(row))
  437. }
  438. },
  439. // 验证选中状态(数据变化时调用)
  440. validateSelection() {
  441. if (!this.sortedData || this.sortedData.length === 0) {
  442. this.clearSelection()
  443. return
  444. }
  445. // 过滤出仍然存在的选中项
  446. const validSelectedRows = []
  447. const validSelectedRowKeys = []
  448. this.selectedRows.forEach(selectedRow => {
  449. const rowKey = this.getRowKey(selectedRow)
  450. const stillExists = this.sortedData.some(row => this.getRowKey(row) === rowKey)
  451. if (stillExists) {
  452. validSelectedRows.push(selectedRow)
  453. validSelectedRowKeys.push(rowKey)
  454. }
  455. })
  456. if (validSelectedRows.length !== this.selectedRows.length) {
  457. this.selectedRows = validSelectedRows
  458. this.selectedRowKeys = validSelectedRowKeys
  459. this.$emit('selectionChange', this.selectedRows)
  460. }
  461. },
  462. // 获取行的唯一标识
  463. getRowKey(row) {
  464. if (!row) return ''
  465. return this.rowKey ? this.getValueByPath(row, this.rowKey) : JSON.stringify(row)
  466. },
  467. // 切换行选中状态
  468. toggleRowSelection(row, selected) {
  469. const rowKey = this.getRowKey(row)
  470. const index = this.selectedRowKeys.indexOf(rowKey)
  471. if (selected === undefined) {
  472. selected = index === -1
  473. }
  474. if (selected && index === -1) {
  475. // 选中
  476. this.selectedRows.push(row)
  477. this.selectedRowKeys.push(rowKey)
  478. } else if (!selected && index !== -1) {
  479. // 取消选中
  480. this.selectedRows.splice(index, 1)
  481. this.selectedRowKeys.splice(index, 1)
  482. }
  483. this.$emit('select', this.selectedRows, row)
  484. this.$emit('selectionChange', this.selectedRows)
  485. },
  486. // 全选/取消全选
  487. handleSelectAll(checked) {
  488. if (checked) {
  489. // 全选
  490. this.selectedRows = [...this.sortedData]
  491. this.selectedRowKeys = this.sortedData.map(row => this.getRowKey(row))
  492. } else {
  493. // 取消全选
  494. this.selectedRows = []
  495. this.selectedRowKeys = []
  496. }
  497. // 强制更新子组件数据,确保复选框状态同步
  498. this.updateChildrenData()
  499. this.$emit('selectAll', this.selectedRows)
  500. this.$emit('selectionChange', this.selectedRows)
  501. },
  502. // 清空选择
  503. clearSelection() {
  504. this.selectedRows = []
  505. this.selectedRowKeys = []
  506. this.$emit('selectionChange', this.selectedRows)
  507. },
  508. // 获取当前选中的行
  509. getSelectionRows() {
  510. return this.selectedRows
  511. },
  512. // 获取嵌套属性值
  513. getValueByPath(obj, path) {
  514. if (!path || !obj) return ''
  515. const keys = path.split('.')
  516. let value = obj
  517. for (const key of keys) {
  518. if (value && typeof value === 'object') {
  519. value = value[key]
  520. } else {
  521. value = ''
  522. break
  523. }
  524. }
  525. return value
  526. },
  527. // 更新子组件数据
  528. updateChildrenData() {
  529. if (this.children && this.children.length > 0) {
  530. this.children.forEach(child => {
  531. if (child.parentData) {
  532. // 传递排序后的数据给子组件
  533. child.parentData.data = this.sortedData
  534. child.parentData.border = this.border
  535. child.parentData.stripe = this.stripe
  536. // 传递合并信息
  537. child.parentData.mergeInfo = this.mergeInfo
  538. // 传递选中状态
  539. if (child.type === 'selection') {
  540. child.parentData.selectedRowKeys = this.selectedRowKeys
  541. child.parentData.toggleRowSelection = this.toggleRowSelection.bind(this)
  542. }
  543. }
  544. })
  545. }
  546. },
  547. // 获取下拉菜单内容的高度
  548. async getHeaderHeight() {
  549. await uni.$u.sleep(30);
  550. // #ifndef APP-NVUE
  551. this.$uGetRect('.u-table__header').then(res => {
  552. this.headerHeight = res.height;
  553. });
  554. // #endif
  555. // #ifdef APP-NVUE
  556. const ref = this.$refs['u-table__header']
  557. ref && dom.getComponentRect(ref, (res) => {
  558. this.headerHeight = res.size.height
  559. });
  560. // #endif
  561. },
  562. // 获取表尾高度
  563. async getFooterHeight() {
  564. if (!this.showSummary) {
  565. this.footerHeight = 0
  566. return
  567. }
  568. await uni.$u.sleep(30);
  569. // #ifndef APP-NVUE
  570. this.$uGetRect('.u-table__footer').then(res => {
  571. this.footerHeight = res.height || 0;
  572. });
  573. // #endif
  574. // #ifdef APP-NVUE
  575. const ref = this.$refs['u-table__footer']
  576. ref && dom.getComponentRect(ref, (res) => {
  577. this.footerHeight = res.size.height || 0
  578. });
  579. // #endif
  580. },
  581. // 表头点击事件处理
  582. handleHeaderClick(column, index) {
  583. if (column.type === 'selection') {
  584. // 多选列不处理排序
  585. return
  586. }
  587. this.handleSort(column, index)
  588. },
  589. handleSort(column, index) {
  590. if (!column.sortable || column.type === 'index') return
  591. let newOrder = 'asc'
  592. // 如果点击的是当前排序列
  593. if (this.sortColumn === column.prop) {
  594. if (this.sortOrder === 'asc') {
  595. // 当前是升序,切换到降序
  596. newOrder = 'desc'
  597. } else if (this.sortOrder === 'desc') {
  598. // 当前是降序,取消排序
  599. this.sortColumn = null
  600. this.sortOrder = null
  601. this.$emit('sortChange', {
  602. prop: null,
  603. order: null,
  604. column: column,
  605. index: index
  606. })
  607. return
  608. }
  609. }
  610. // 设置新的排序状态
  611. this.sortColumn = column.prop
  612. this.sortOrder = newOrder
  613. // 触发排序变化事件
  614. this.$emit('sortChange', {
  615. prop: this.sortColumn,
  616. order: this.sortOrder,
  617. column: column,
  618. index: index
  619. })
  620. }
  621. }
  622. }
  623. </script>
  624. <style lang="scss" scoped>
  625. @import "../../libs/css/components.scss";
  626. .u-table {
  627. position: relative;
  628. flex:1;
  629. &__header {
  630. @include flex(row);
  631. background-color: $u-bg-color;
  632. flex-shrink: 0;
  633. &__column {
  634. @include flex(row);
  635. flex-shrink: 0;
  636. &:last-child .is-border {
  637. border-right: none;
  638. }
  639. }
  640. }
  641. &__footer {
  642. @include flex(row);
  643. background-color: $u-bg-color;
  644. flex-shrink: 0;
  645. border-top: 1px solid $u-border-color;
  646. &__column {
  647. @include flex(row);
  648. flex-shrink: 0;
  649. &:last-child .is-border {
  650. border-right: none;
  651. }
  652. }
  653. }
  654. &__body {
  655. flex: 1;
  656. width: 100%;
  657. }
  658. &__rows {
  659. @include flex(row);
  660. width: 100%;
  661. }
  662. &__cell {
  663. @include flex(row);
  664. flex: 1;
  665. padding: 0px 10px;
  666. align-items: center;
  667. position: relative;
  668. &.is-left {
  669. justify-content: flex-start;
  670. }
  671. &.is-center {
  672. justify-content: center;
  673. }
  674. &.is-right {
  675. justify-content: flex-end;
  676. }
  677. &.is-border {
  678. border-right: 1px solid $u-border-color;
  679. border-bottom: 1px solid $u-border-color;
  680. }
  681. }
  682. &__summary-cell {
  683. font-weight: 500;
  684. color: $u-main-color;
  685. background-color: #fafafa;
  686. &.is-border {
  687. border-bottom: none;
  688. }
  689. }
  690. &__value {
  691. font-size: 13px;
  692. line-height: 15px;
  693. &.is-ellipsis {
  694. word-break: break-all;
  695. display: -webkit-box;
  696. -webkit-box-orient: vertical;
  697. -webkit-line-clamp: 1;
  698. overflow: hidden;
  699. }
  700. }
  701. &.is-border {
  702. border: 1px solid $u-border-color;
  703. }
  704. }
  705. .u-table__sort-icon {
  706. @include flex(column);
  707. margin-left: 4px;
  708. gap: 2px;
  709. }
  710. .u-table__sort-arrow {
  711. width: 0;
  712. height: 0;
  713. border-left: 4px solid transparent;
  714. border-right: 4px solid transparent;
  715. transition: border-color 0.3s ease;
  716. &--up {
  717. border-bottom: 5px solid #c0c4cc;
  718. &.is-active {
  719. border-bottom-color: $u-primary;
  720. }
  721. }
  722. &--down {
  723. border-top: 5px solid #c0c4cc;
  724. &.is-active {
  725. border-top-color: $u-primary;
  726. }
  727. }
  728. }
  729. .u-table__empty {
  730. @include flex(column);
  731. align-items: center;
  732. justify-content: center;
  733. color: #909399;
  734. font-size: 14px;
  735. width: 100%;
  736. }
  737. .u-table__empty-content {
  738. text-align: center;
  739. }
  740. .u-table__empty-text {
  741. color: #909399;
  742. }
  743. </style>