uni-table.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. <template>
  2. <view
  3. class="uni-table-scroll"
  4. :class="{ 'table--border': border, 'border-none': !noData }"
  5. >
  6. <!-- #ifdef H5 -->
  7. <table
  8. class="uni-table"
  9. border="0"
  10. cellpadding="0"
  11. cellspacing="0"
  12. :class="{ 'table--stripe': stripe }"
  13. :style="{ 'min-width': minWidth + 'px' }"
  14. >
  15. <slot></slot>
  16. <view v-if="noData" class="uni-table-loading">
  17. <view class="uni-table-text" :class="{ 'empty-border': border }">{{
  18. emptyText
  19. }}</view>
  20. </view>
  21. <view
  22. v-if="loading"
  23. class="uni-table-mask"
  24. :class="{ 'empty-border': border }"
  25. ><div class="uni-table--loader"></div
  26. ></view>
  27. </table>
  28. <!-- #endif -->
  29. <!-- #ifndef H5 -->
  30. <view
  31. class="uni-table"
  32. :style="{ 'min-width': minWidth + 'px' }"
  33. :class="{ 'table--stripe': stripe }"
  34. >
  35. <slot></slot>
  36. <view v-if="noData" class="uni-table-loading">
  37. <view class="uni-table-text" :class="{ 'empty-border': border }">{{
  38. emptyText
  39. }}</view>
  40. </view>
  41. <view
  42. v-if="loading"
  43. class="uni-table-mask"
  44. :class="{ 'empty-border': border }"
  45. ><div class="uni-table--loader"></div
  46. ></view>
  47. </view>
  48. <!-- #endif -->
  49. </view>
  50. </template>
  51. <script>
  52. /**
  53. * Table 表格
  54. * @description 用于展示多条结构类似的数据
  55. * @tutorial https://ext.dcloud.net.cn/plugin?id=3270
  56. * @property {Boolean} border 是否带有纵向边框
  57. * @property {Boolean} stripe 是否显示斑马线
  58. * @property {Boolean} type 是否开启多选
  59. * @property {String} emptyText 空数据时显示的文本内容
  60. * @property {Boolean} loading 显示加载中
  61. * @event {Function} selection-change 开启多选时,当选择项发生变化时会触发该事件
  62. */
  63. export default {
  64. name: 'uniTable',
  65. options: {
  66. virtualHost: true
  67. },
  68. emits: ['selection-change'],
  69. props: {
  70. data: {
  71. type: Array,
  72. default () {
  73. return []
  74. }
  75. },
  76. // 是否有竖线
  77. border: {
  78. type: Boolean,
  79. default: false
  80. },
  81. // 是否显示斑马线
  82. stripe: {
  83. type: Boolean,
  84. default: false
  85. },
  86. // 多选
  87. type: {
  88. type: String,
  89. default: ''
  90. },
  91. // 没有更多数据
  92. emptyText: {
  93. type: String,
  94. default: '没有更多数据'
  95. },
  96. loading: {
  97. type: Boolean,
  98. default: false
  99. },
  100. rowKey: {
  101. type: String,
  102. default: ''
  103. }
  104. },
  105. data () {
  106. return {
  107. noData: true,
  108. minWidth: 0,
  109. multiTableHeads: []
  110. }
  111. },
  112. watch: {
  113. loading (val) {},
  114. data (newVal) {
  115. let theadChildren = this.theadChildren
  116. let rowspan = 1
  117. if (this.theadChildren) {
  118. rowspan = this.theadChildren.rowspan
  119. }
  120. // this.trChildren.length - rowspan
  121. this.noData = false
  122. // this.noData = newVal.length === 0
  123. }
  124. },
  125. created () {
  126. // 定义tr的实例数组
  127. this.trChildren = []
  128. this.thChildren = []
  129. this.theadChildren = null
  130. this.backData = []
  131. this.backIndexData = []
  132. },
  133. methods: {
  134. isNodata () {
  135. let theadChildren = this.theadChildren
  136. let rowspan = 1
  137. if (this.theadChildren) {
  138. rowspan = this.theadChildren.rowspan
  139. }
  140. this.noData = this.trChildren.length - rowspan <= 0
  141. },
  142. /**
  143. * 选中所有
  144. */
  145. selectionAll () {
  146. let startIndex = 1
  147. let theadChildren = this.theadChildren
  148. if (!this.theadChildren) {
  149. theadChildren = this.trChildren[0]
  150. } else {
  151. startIndex = theadChildren.rowspan - 1
  152. }
  153. let isHaveData = this.data && this.data.length > 0
  154. theadChildren.checked = true
  155. theadChildren.indeterminate = false
  156. this.trChildren.forEach((item, index) => {
  157. if (!item.disabled) {
  158. item.checked = true
  159. if (isHaveData && item.keyValue) {
  160. const row = this.data.find(v => v[this.rowKey] === item.keyValue)
  161. if (!this.backData.find(v => v[this.rowKey] === row[this.rowKey])) {
  162. this.backData.push(row)
  163. }
  164. }
  165. if (
  166. index > startIndex - 1 &&
  167. this.backIndexData.indexOf(index - startIndex) === -1
  168. ) {
  169. this.backIndexData.push(index - startIndex)
  170. }
  171. }
  172. })
  173. // this.backData = JSON.parse(JSON.stringify(this.data))
  174. this.$emit('selection-change', {
  175. detail: {
  176. value: this.backData,
  177. index: this.backIndexData
  178. }
  179. })
  180. },
  181. /**
  182. * 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中)
  183. */
  184. toggleRowSelection (row, selected) {
  185. // if (!this.theadChildren) return
  186. row = [].concat(row)
  187. this.trChildren.forEach((item, index) => {
  188. // if (item.keyValue) {
  189. const select = row.findIndex(v => {
  190. //
  191. if (typeof v === 'number') {
  192. return v === index - 1
  193. } else {
  194. return v[this.rowKey] === item.keyValue
  195. }
  196. })
  197. let ischeck = item.checked
  198. if (select !== -1) {
  199. if (typeof selected === 'boolean') {
  200. item.checked = selected
  201. } else {
  202. item.checked = !item.checked
  203. }
  204. if (ischeck !== item.checked) {
  205. this.check(
  206. item.rowData || item,
  207. item.checked,
  208. item.rowData ? item.keyValue : null,
  209. true
  210. )
  211. }
  212. }
  213. // }
  214. })
  215. this.$emit('selection-change', {
  216. detail: {
  217. value: this.backData,
  218. index: this.backIndexData
  219. }
  220. })
  221. },
  222. /**
  223. * 用于多选表格,清空用户的选择
  224. */
  225. clearSelection () {
  226. let theadChildren = this.theadChildren
  227. if (!this.theadChildren) {
  228. theadChildren = this.trChildren[0]
  229. }
  230. // if (!this.theadChildren) return
  231. theadChildren.checked = false
  232. theadChildren.indeterminate = false
  233. this.trChildren.forEach(item => {
  234. // if (item.keyValue) {
  235. item.checked = false
  236. // }
  237. })
  238. this.backData = []
  239. this.backIndexData = []
  240. this.$emit('selection-change', {
  241. detail: {
  242. value: [],
  243. index: []
  244. }
  245. })
  246. },
  247. /**
  248. * 用于多选表格,切换所有行的选中状态
  249. */
  250. toggleAllSelection () {
  251. let list = []
  252. let startIndex = 1
  253. let theadChildren = this.theadChildren
  254. if (!this.theadChildren) {
  255. theadChildren = this.trChildren[0]
  256. } else {
  257. startIndex = theadChildren.rowspan - 1
  258. }
  259. this.trChildren.forEach((item, index) => {
  260. if (!item.disabled) {
  261. if (index > startIndex - 1) {
  262. list.push(index - startIndex)
  263. }
  264. }
  265. })
  266. this.toggleRowSelection(list)
  267. },
  268. /**
  269. * 选中\取消选中
  270. * @param {Object} child
  271. * @param {Object} check
  272. * @param {Object} rowValue
  273. */
  274. check (child, check, keyValue, emit) {
  275. let theadChildren = this.theadChildren
  276. if (!this.theadChildren) {
  277. theadChildren = this.trChildren[0]
  278. }
  279. let childDomIndex = this.trChildren.findIndex(
  280. (item, index) => child === item
  281. )
  282. if (childDomIndex < 0) {
  283. childDomIndex =
  284. this.data.findIndex(v => v[this.rowKey] === keyValue) + 1
  285. }
  286. const dataLen = this.trChildren.filter(
  287. v => !v.disabled && v.keyValue
  288. ).length
  289. if (childDomIndex === 0) {
  290. check ? this.selectionAll() : this.clearSelection()
  291. return
  292. }
  293. if (check) {
  294. if (keyValue) {
  295. this.backData.push(child)
  296. }
  297. this.backIndexData.push(childDomIndex - 1)
  298. } else {
  299. const index = this.backData.findIndex(v => v[this.rowKey] === keyValue)
  300. const idx = this.backIndexData.findIndex(
  301. item => item === childDomIndex - 1
  302. )
  303. if (keyValue) {
  304. this.backData.splice(index, 1)
  305. }
  306. this.backIndexData.splice(idx, 1)
  307. }
  308. const domCheckAll = this.trChildren.find(
  309. (item, index) => index > 0 && !item.checked && !item.disabled
  310. )
  311. if (!domCheckAll) {
  312. theadChildren.indeterminate = false
  313. theadChildren.checked = true
  314. } else {
  315. theadChildren.indeterminate = true
  316. theadChildren.checked = false
  317. }
  318. if (this.backIndexData.length === 0) {
  319. theadChildren.indeterminate = false
  320. }
  321. if (!emit) {
  322. this.$emit('selection-change', {
  323. detail: {
  324. value: this.backData,
  325. index: this.backIndexData
  326. }
  327. })
  328. }
  329. }
  330. }
  331. }
  332. </script>
  333. <style lang="scss">
  334. $border-color: #ebeef5;
  335. .uni-table-scroll {
  336. width: 100%;
  337. /* #ifndef APP-NVUE */
  338. overflow-x: auto;
  339. /* #endif */
  340. }
  341. .uni-table {
  342. position: relative;
  343. width: 100%;
  344. border-radius: 5px;
  345. // box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.1);
  346. background-color: #fff;
  347. /* #ifndef APP-NVUE */
  348. box-sizing: border-box;
  349. display: table;
  350. overflow-x: auto;
  351. ::v-deep .uni-table-tr:nth-child(n + 2) {
  352. &:hover {
  353. background-color: #f5f7fa;
  354. }
  355. }
  356. ::v-deep .uni-table-thead {
  357. .uni-table-tr {
  358. // background-color: #f5f7fa;
  359. &:hover {
  360. background-color: #fafafa;
  361. }
  362. }
  363. }
  364. /* #endif */
  365. }
  366. .table--border {
  367. border: 1px $border-color solid;
  368. border-right: none;
  369. }
  370. .border-none {
  371. /* #ifndef APP-NVUE */
  372. border-bottom: none;
  373. /* #endif */
  374. }
  375. .table--stripe {
  376. /* #ifndef APP-NVUE */
  377. ::v-deep .uni-table-tr:nth-child(2n + 3) {
  378. background-color: #fafafa;
  379. }
  380. /* #endif */
  381. }
  382. /* 表格加载、无数据样式 */
  383. .uni-table-loading {
  384. position: relative;
  385. /* #ifndef APP-NVUE */
  386. display: table-row;
  387. /* #endif */
  388. height: 50px;
  389. line-height: 50px;
  390. overflow: hidden;
  391. box-sizing: border-box;
  392. }
  393. .empty-border {
  394. border-right: 1px $border-color solid;
  395. }
  396. .uni-table-text {
  397. position: absolute;
  398. right: 0;
  399. left: 0;
  400. text-align: center;
  401. font-size: 32rpx;
  402. color: #999;
  403. }
  404. .uni-table-mask {
  405. position: absolute;
  406. top: 0;
  407. bottom: 0;
  408. left: 0;
  409. right: 0;
  410. background-color: rgba(255, 255, 255, 0.8);
  411. z-index: 99;
  412. /* #ifndef APP-NVUE */
  413. display: flex;
  414. margin: auto;
  415. transition: all 0.5s;
  416. /* #endif */
  417. justify-content: center;
  418. align-items: center;
  419. }
  420. .uni-table--loader {
  421. width: 30px;
  422. height: 30px;
  423. border: 2px solid #aaa;
  424. // border-bottom-color: transparent;
  425. border-radius: 50%;
  426. /* #ifndef APP-NVUE */
  427. animation: 2s uni-table--loader linear infinite;
  428. /* #endif */
  429. position: relative;
  430. }
  431. @keyframes uni-table--loader {
  432. 0% {
  433. transform: rotate(360deg);
  434. }
  435. 10% {
  436. border-left-color: transparent;
  437. }
  438. 20% {
  439. border-bottom-color: transparent;
  440. }
  441. 30% {
  442. border-right-color: transparent;
  443. }
  444. 40% {
  445. border-top-color: transparent;
  446. }
  447. 50% {
  448. transform: rotate(0deg);
  449. }
  450. 60% {
  451. border-top-color: transparent;
  452. }
  453. 70% {
  454. border-left-color: transparent;
  455. }
  456. 80% {
  457. border-bottom-color: transparent;
  458. }
  459. 90% {
  460. border-right-color: transparent;
  461. }
  462. 100% {
  463. transform: rotate(-360deg);
  464. }
  465. }
  466. </style>