basic-table.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  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" :style="{fontSize:fontSize+'px'}">{{ 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" :style="{fontSize:fontSize+'px'}">{{ 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 :style="{fontSize:fontSize+'px'}">{{ 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. fontSize:{
  143. type: Number,
  144. default: 15
  145. },
  146. indexWidth: {
  147. type: String,
  148. default: '60px'
  149. }
  150. },
  151. data() {
  152. return {
  153. sumList: [],
  154. tableWidth: 0
  155. };
  156. },
  157. mounted() {
  158. const query = uni.createSelectorQuery().in(this).select('.base-table');
  159. query.boundingClientRect(data => {
  160. this.tableWidth = data.width;
  161. }).exec();
  162. },
  163. computed: {
  164. getTableStyle() {
  165. const { width, height, maxHeight } = this;
  166. const styleObj = {};
  167. if (width) {
  168. styleObj.width = width;
  169. }
  170. if (height) {
  171. styleObj.height = height;
  172. }
  173. if (maxHeight) {
  174. styleObj.maxHeight = maxHeight;
  175. }
  176. return styleObj;
  177. },
  178. tableBodyStyle() {
  179. if (!this.tableWidth) return {};
  180. const clienWidth = this.tableWidth;
  181. const flexColumn = this.columns.filter(item => !item.width);
  182. //set min width
  183. const minWidth = this.minItemWidth;
  184. let bodyMinWidth = this.columns.reduce((t, c) => {
  185. c.width = c.width || minWidth;
  186. return t + parseFloat(c.width);
  187. }, 0);
  188. if(this.indexShow){
  189. bodyMinWidth+=parseFloat(this.indexWidth)
  190. }
  191. if (flexColumn.length > 0 && bodyMinWidth < clienWidth) {
  192. const flexWidth = clienWidth - bodyMinWidth;
  193. if (flexColumn.length === 1) {
  194. flexColumn[0].width = minWidth + flexWidth;
  195. } else {
  196. const scaleWidth = flexWidth / flexColumn.length;
  197. flexColumn.forEach(item => {
  198. item.width = minWidth + Math.floor(scaleWidth);
  199. });
  200. }
  201. }
  202. bodyMinWidth = Math.max(bodyMinWidth, clienWidth);
  203. return {
  204. width: `${bodyMinWidth}px`
  205. };
  206. },
  207. showXScroll() {
  208. const clienWidth = this.tableWidth;
  209. return clienWidth < parseFloat(this.tableBodyStyle?.width || 0);
  210. },
  211. isEmpty() {
  212. return this.data.length === 0;
  213. },
  214. getHeaderClass() {
  215. const headerClass = [];
  216. if (this.headerRowClassName) {
  217. headerClass.push(this.headerRowClassName);
  218. }
  219. return headerClass;
  220. },
  221. getHeaderStyle() {
  222. const headerStyle = [];
  223. if (typeof this.headerRowStyle === 'object') {
  224. if (this.headerRowStyle) {
  225. headerStyle.push(this.headerRowStyle);
  226. }
  227. }
  228. return headerStyle;
  229. },
  230. getIndexColStyle() {
  231. return {
  232. textAlign: this.align,
  233. width: this.indexWidth
  234. };
  235. }
  236. },
  237. methods: {
  238. init() {
  239. this.sumList = [];
  240. if (this.showFooter && this.data.length > 0) {
  241. const { columns, data, footerText } = this;
  242. if (typeof this.footerMethod === 'function') {
  243. this.sumList = this.footerMethod({ columns, data });
  244. } else {
  245. columns.forEach((column, index) => {
  246. if (!this.indexShow && index === 0) {
  247. this.sumList[index] = footerText;
  248. return;
  249. }
  250. const values = data.map(item => Number(item[column.fieldName]));
  251. const precisions = [];
  252. let notNumber = true;
  253. values.forEach(value => {
  254. if (!Number.isNaN(+value)) {
  255. notNumber = false;
  256. const decimal = `${value}`.split('.')[1];
  257. precisions.push(decimal ? decimal.length : 0);
  258. }
  259. });
  260. const precision = Math.max.apply(null, precisions);
  261. if (!notNumber) {
  262. this.sumList[index] = values.reduce((prev, curr) => {
  263. const value = Number(curr);
  264. if (!Number.isNaN(+value)) {
  265. return Number.parseFloat((prev + curr).toFixed(Math.min(precision, 20)));
  266. } else {
  267. return prev;
  268. }
  269. }, 0);
  270. } else {
  271. this.sumList[index] = '';
  272. }
  273. });
  274. }
  275. }
  276. },
  277. getBodyClass(scope, index) {
  278. const bodyClass = [];
  279. if (this.stripe) {
  280. bodyClass.push({ 'is-stripe': index % 2 === 1 });
  281. }
  282. if (typeof this.rowClassName === 'function') {
  283. const rowClass = this.rowClassName?.(scope, index);
  284. if (rowClass) {
  285. bodyClass.push(rowClass);
  286. }
  287. } else if (typeof this.rowClassName === 'string') {
  288. if (this.rowClassName) {
  289. bodyClass.push(this.rowClassName);
  290. }
  291. }
  292. return bodyClass;
  293. },
  294. getBodyStyle(scope, index) {
  295. const bodyStyle = [];
  296. if (typeof this.rowStyle === 'function') {
  297. const rowStyle = this.rowStyle?.(scope, index);
  298. if (rowStyle) {
  299. bodyStyle.push(rowStyle);
  300. }
  301. } else if (typeof this.rowStyle === 'object') {
  302. if (this.rowStyle) {
  303. bodyStyle.push(this.rowStyle);
  304. }
  305. }
  306. return bodyStyle;
  307. },
  308. getIndexMethod(index) {
  309. let curIndex = index + 1;
  310. if (typeof this.indexMethod === 'function') {
  311. curIndex = this.indexMethod?.(index);
  312. }
  313. return curIndex;
  314. },
  315. getCellProps(row) {
  316. const classList = [];
  317. if (this.showXScroll && row.fixed) {
  318. classList.push('fixed');
  319. if (row.fixed === 'left') {
  320. classList.push('fixed-left');
  321. } else {
  322. classList.push('fixed-right');
  323. }
  324. }
  325. return {
  326. class: classList,
  327. style: {
  328. width: `${row.width}px`,
  329. textAlign: this.align,
  330. minWidth: `${this.minItemWidth}px`
  331. }
  332. };
  333. },
  334. handleHeaderClick() {
  335. this.$emit('header-click');
  336. },
  337. handleRowClick(scope, index) {
  338. this.$emit('row-click', scope, index);
  339. },
  340. handleCellClick(scope, column, index) {
  341. this.$emit('cell-click', { scope, column, index });
  342. },
  343. },
  344. watch: {
  345. data: {
  346. handler() {
  347. this.init();
  348. },
  349. immediate: true,
  350. deep: true
  351. }
  352. }
  353. };
  354. </script>
  355. <style lang="scss" scoped>
  356. @import './basic-table.scss';
  357. </style>