basic-table.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. <template>
  2. <view class="base-table" :style="[getTableStyle]" :class="{ 'is-border': border, 'no-data': data.length === 0 }">
  3. <view class="base-table-inner">
  4. <view class="base-table-header" v-if="showHeader">
  5. <view class="b-table" :style="[tableBodyStyle]">
  6. <view class="b-thead">
  7. <view class="b-tr" :class="getHeaderClass" :style="getHeaderStyle" @click="handleHeaderClick">
  8. <view class="b-th" v-if="indexShow" :style="[getIndexColStyle]"><view class="b-cell">序号</view></view>
  9. <view class="b-th" v-for="item in columns" :key="item.fieldName" :class="[getCellProps(item).class]" :style="[getCellProps(item).style]">
  10. <view class="b-cell">{{ item.fieldDesc }}</view>
  11. </view>
  12. </view>
  13. </view>
  14. </view>
  15. </view>
  16. <view class="base-table-body">
  17. <view class="b-table" :style="[tableBodyStyle]">
  18. <view class="b-tbody" v-if="data.length > 0">
  19. <view
  20. class="b-tr"
  21. v-for="(scope, index) in data"
  22. :key="index"
  23. :class="[getBodyClass(scope, index)]"
  24. :style="[getBodyStyle(scope, index)]"
  25. @click="handleRowClick(scope, index)"
  26. >
  27. <view class="b-td" v-if="indexShow" :style="[getIndexColStyle]">
  28. <view class="b-cell">{{ getIndexMethod(index) }}</view>
  29. </view>
  30. <view class="b-td" v-for="column in columns" :key="column.fieldName" :class="[getCellProps(column).class]" :style="[getCellProps(column).style]">
  31. <view class="b-cell" @click.stop="handleCellClick(scope, column, index)">
  32. <slot name="item" :scope="scope" :column="column" v-if="column.fieldType === 'slot'"></slot>
  33. <view v-else>{{ scope[column.fieldName] }}</view>
  34. </view>
  35. </view>
  36. </view>
  37. </view>
  38. <view class="base-table-empty" v-else>
  39. <view class="mt20" v-if="!$slots.empty">{{ emptyText }}</view>
  40. <slot name="empty"></slot>
  41. </view>
  42. </view>
  43. </view>
  44. <view class="base-table-footer" v-if="showFooter">
  45. <view class="b-table" :style="[tableBodyStyle]">
  46. <view class="b-tbody">
  47. <view class="b-tr">
  48. <view class="b-td" v-if="indexShow" :style="[getIndexColStyle]">
  49. <view class="b-cell">{{ footerText }}</view>
  50. </view>
  51. <view
  52. class="b-td"
  53. v-for="(item, index) in sumList"
  54. :key="index"
  55. :class="[getCellProps(columns[index]).class]"
  56. :style="[getCellProps(columns[index]).style]"
  57. >
  58. <view class="b-cell">{{ item }}</view>
  59. </view>
  60. </view>
  61. </view>
  62. </view>
  63. </view>
  64. </view>
  65. </view>
  66. </template>
  67. <script>
  68. export default {
  69. props: {
  70. columns: {
  71. type: Array,
  72. default: () => []
  73. },
  74. data: {
  75. type: Array,
  76. default: () => []
  77. },
  78. align: {
  79. type: String,
  80. default: 'left'
  81. },
  82. height: {
  83. type: String
  84. },
  85. maxHeight: {
  86. type: String
  87. },
  88. width: {
  89. type: String,
  90. default: '100%'
  91. },
  92. emptyText: {
  93. type: String,
  94. default: '暂无数据'
  95. },
  96. border: {
  97. type: Boolean,
  98. default: false
  99. },
  100. stripe: {
  101. type: Boolean,
  102. default: false
  103. },
  104. showHeader: {
  105. type: Boolean,
  106. default: true
  107. },
  108. showFooter: {
  109. type: Boolean,
  110. default: false
  111. },
  112. footerMethod: {
  113. type: Function
  114. },
  115. footerText: {
  116. type: String,
  117. default: '合计'
  118. },
  119. indexShow: {
  120. type: Boolean,
  121. default: false
  122. },
  123. minItemWidth: {
  124. type: Number,
  125. default: 80
  126. },
  127. rowClassName: {
  128. type: [Function, String]
  129. },
  130. rowStyle: {
  131. type: [Function, Object]
  132. },
  133. indexMethod: {
  134. type: Function
  135. },
  136. headerRowClassName: {
  137. type: String
  138. },
  139. headerRowStyle: {
  140. type: Object
  141. },
  142. indexWidth: {
  143. type: String,
  144. default: '60px'
  145. }
  146. },
  147. data() {
  148. return {
  149. sumList: [],
  150. tableWidth: 0
  151. };
  152. },
  153. mounted() {
  154. const query = uni.createSelectorQuery().in(this).select('.base-table');
  155. query.boundingClientRect(data => {
  156. this.tableWidth = data.width;
  157. }).exec();
  158. },
  159. computed: {
  160. getTableStyle() {
  161. const { width, height, maxHeight } = this;
  162. const styleObj = {};
  163. if (width) {
  164. styleObj.width = width;
  165. }
  166. if (height) {
  167. styleObj.height = height;
  168. }
  169. if (maxHeight) {
  170. styleObj.maxHeight = maxHeight;
  171. }
  172. return styleObj;
  173. },
  174. tableBodyStyle() {
  175. if (!this.tableWidth) return {};
  176. const clienWidth = this.tableWidth;
  177. const flexColumn = this.columns.filter(item => !item.width);
  178. //set min width
  179. const minWidth = this.minItemWidth;
  180. let bodyMinWidth = this.columns.reduce((t, c) => {
  181. c.width = c.width || minWidth;
  182. return t + parseFloat(c.width);
  183. }, 0);
  184. if(this.indexShow){
  185. bodyMinWidth+=parseFloat(this.indexWidth)
  186. }
  187. if (flexColumn.length > 0 && bodyMinWidth < clienWidth) {
  188. const flexWidth = clienWidth - bodyMinWidth;
  189. if (flexColumn.length === 1) {
  190. flexColumn[0].width = minWidth + flexWidth;
  191. } else {
  192. const scaleWidth = flexWidth / flexColumn.length;
  193. flexColumn.forEach(item => {
  194. item.width = minWidth + Math.floor(scaleWidth);
  195. });
  196. }
  197. }
  198. bodyMinWidth = Math.max(bodyMinWidth, clienWidth);
  199. return {
  200. width: `${bodyMinWidth}px`
  201. };
  202. },
  203. showXScroll() {
  204. const clienWidth = this.tableWidth;
  205. return clienWidth < parseFloat(this.tableBodyStyle?.width || 0);
  206. },
  207. isEmpty() {
  208. return this.data.length === 0;
  209. },
  210. getHeaderClass() {
  211. const headerClass = [];
  212. if (this.headerRowClassName) {
  213. headerClass.push(this.headerRowClassName);
  214. }
  215. return headerClass;
  216. },
  217. getHeaderStyle() {
  218. const headerStyle = [];
  219. if (typeof this.headerRowStyle === 'object') {
  220. if (this.headerRowStyle) {
  221. headerStyle.push(this.headerRowStyle);
  222. }
  223. }
  224. return headerStyle;
  225. },
  226. getIndexColStyle() {
  227. return {
  228. textAlign: this.align,
  229. width: this.indexWidth
  230. };
  231. }
  232. },
  233. methods: {
  234. init() {
  235. this.sumList = [];
  236. if (this.showFooter && this.data.length > 0) {
  237. const { columns, data, footerText } = this;
  238. if (typeof this.footerMethod === 'function') {
  239. this.sumList = this.footerMethod({ columns, data });
  240. } else {
  241. columns.forEach((column, index) => {
  242. if (!this.indexShow && index === 0) {
  243. this.sumList[index] = footerText;
  244. return;
  245. }
  246. const values = data.map(item => Number(item[column.fieldName]));
  247. const precisions = [];
  248. let notNumber = true;
  249. values.forEach(value => {
  250. if (!Number.isNaN(+value)) {
  251. notNumber = false;
  252. const decimal = `${value}`.split('.')[1];
  253. precisions.push(decimal ? decimal.length : 0);
  254. }
  255. });
  256. const precision = Math.max.apply(null, precisions);
  257. if (!notNumber) {
  258. this.sumList[index] = values.reduce((prev, curr) => {
  259. const value = Number(curr);
  260. if (!Number.isNaN(+value)) {
  261. return Number.parseFloat((prev + curr).toFixed(Math.min(precision, 20)));
  262. } else {
  263. return prev;
  264. }
  265. }, 0);
  266. } else {
  267. this.sumList[index] = '';
  268. }
  269. });
  270. }
  271. }
  272. },
  273. getBodyClass(scope, index) {
  274. const bodyClass = [];
  275. if (this.stripe) {
  276. bodyClass.push({ 'is-stripe': index % 2 === 1 });
  277. }
  278. if (typeof this.rowClassName === 'function') {
  279. const rowClass = this.rowClassName?.(scope, index);
  280. if (rowClass) {
  281. bodyClass.push(rowClass);
  282. }
  283. } else if (typeof this.rowClassName === 'string') {
  284. if (this.rowClassName) {
  285. bodyClass.push(this.rowClassName);
  286. }
  287. }
  288. return bodyClass;
  289. },
  290. getBodyStyle(scope, index) {
  291. const bodyStyle = [];
  292. if (typeof this.rowStyle === 'function') {
  293. const rowStyle = this.rowStyle?.(scope, index);
  294. if (rowStyle) {
  295. bodyStyle.push(rowStyle);
  296. }
  297. } else if (typeof this.rowStyle === 'object') {
  298. if (this.rowStyle) {
  299. bodyStyle.push(this.rowStyle);
  300. }
  301. }
  302. return bodyStyle;
  303. },
  304. getIndexMethod(index) {
  305. let curIndex = index + 1;
  306. if (typeof this.indexMethod === 'function') {
  307. curIndex = this.indexMethod?.(index);
  308. }
  309. return curIndex;
  310. },
  311. getCellProps(row) {
  312. const classList = [];
  313. if (this.showXScroll && row.fixed) {
  314. classList.push('fixed');
  315. if (row.fixed === 'left') {
  316. classList.push('fixed-left');
  317. } else {
  318. classList.push('fixed-right');
  319. }
  320. }
  321. return {
  322. class: classList,
  323. style: {
  324. width: `${row.width}px`,
  325. textAlign: this.align,
  326. minWidth: `${this.minItemWidth}px`
  327. }
  328. };
  329. },
  330. handleHeaderClick() {
  331. this.$emit('header-click');
  332. },
  333. handleRowClick(scope, index) {
  334. this.$emit('row-click', scope, index);
  335. },
  336. handleCellClick(scope, column, index) {
  337. this.$emit('cell-click', { scope, column, index });
  338. },
  339. },
  340. watch: {
  341. data: {
  342. handler() {
  343. this.init();
  344. },
  345. immediate: true,
  346. deep: true
  347. }
  348. }
  349. };
  350. </script>
  351. <style lang="scss" scoped>
  352. @import './basic-table.scss';
  353. </style>