自主产品,供应链食堂系统。将两个端拆开了。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1317 lines
38 KiB

5 years ago
  1. <template>
  2. <view class="wyb-table-box" :class="randomClass">
  3. <!-- loading加载框 -->
  4. <view v-if="loading" class="wyb-table-loading-box" :style="{
  5. 'max-width': width === 'auto' ? screenWidth : width,
  6. 'max-height': height === 'auto' ? '300rpx' : height,
  7. backgroundColor: loaderBgColor,
  8. borderTop: '1px solid' + borderColor,
  9. borderBottom: '1px solid' + borderColor,
  10. borderLeft: showLeftAndRightBorder ? '1px solid' + borderColor : 'none',
  11. borderRight: showLeftAndRightBorder ? '1px solid' + borderColor : 'none'}">
  12. <view class="loader-one" :style="{
  13. width: loaderSize + 'rpx',
  14. height: loaderSize + 'rpx',
  15. borderTop: '3px solid ' + loadingColor.top,
  16. borderRight: '3px solid ' + loadingColor.right,
  17. borderBottom: '3px solid ' + loadingColor.bottom,
  18. borderLeft: '3px solid ' + loadingColor.left}" />
  19. </view>
  20. <!-- table主体 -->
  21. <view v-if="!loading" class="wyb-table-scroll-view" :style="{
  22. 'max-width': width,
  23. 'max-height': height}">
  24. <!-- borderTop: '1px solid' + borderColor,
  25. borderLeft: showLeftAndRightBorder ? '1px solid' + borderColor : 'none',
  26. borderRight: showLeftAndRightBorder ? '1px solid' + borderColor : 'none' -->
  27. <view class="wyb-table-header" :style="{borderBottom: '1px solid' + borderColor}">
  28. <view class="wyb-table-header-item" v-if="enableCheck" :style="{
  29. minWidth: checkColWidth + 'rpx',
  30. maxWidth: checkColWidth + 'rpx',
  31. minHeight: minHeight[0] + 'rpx',
  32. textAlign: textAlign,
  33. justifyContent: textAlign === 'center' ? textAlign : (textAlign === 'left' ? 'flex-start' : 'flex-end'),
  34. fontSize: fontSize[0] + 'rpx',
  35. color: headerFtColor,
  36. padding: padding[0] + 'rpx ' + (padding[1] || padding[0]) + 'rpx',
  37. backgroundColor: headerBgColor,
  38. borderRight: '1px solid' + borderColor,
  39. zIndex: 30,
  40. left: 0,
  41. color: headerFtColor,
  42. backgroundColor: headerBgColor,
  43. position: 'sticky'}">
  44. <view
  45. class="wyb-table-checkbox"
  46. v-if="enableCheck === 'multiple'"
  47. @tap.stop="onCheckAllTap"
  48. :style="{
  49. width: checkColWidth * 0.5 + 'rpx',
  50. height: checkColWidth * 0.5 + 'rpx',
  51. backgroundColor: checkerBoxBgColor,
  52. border: '1px solid ' + checkerBorderColor}">
  53. <text
  54. class="iconfont icon-check"
  55. v-show="checkAll"
  56. :style="{
  57. color: checkerColor,
  58. backgroundColor: checkerBgColor,
  59. paddingTop: (fontSize[1] || fontSize[0]) * 0.15 + 'rpx',
  60. fontSize: (fontSize[1] || fontSize[0]) + 'rpx'}" />
  61. </view>
  62. </view>
  63. <view ref="iosBug" class="wyb-table-header-item" v-for="(item, index) in headers" :key="item.key" @tap="onHeaderItemTap(index)"
  64. :style="{
  65. minWidth: (item.width || defaultColWidth) + 'rpx',
  66. maxWidth: (item.width || defaultColWidth) + 'rpx',
  67. minHeight: minHeight[0] + 'rpx',
  68. textAlign: textAlign,
  69. justifyContent: textAlign === 'center' ? textAlign : (textAlign === 'left' ? 'flex-start' : 'flex-end'),
  70. fontSize: fontSize[0] + 'rpx',
  71. fontWeight: headerWeight ? 'bold' : 'normal',
  72. color: headerFtColor,
  73. padding: padding[0] + 'rpx ' + (padding[1] || padding[0]) + 'rpx',
  74. backgroundColor: headerBgColor,
  75. borderRight: index === headers.length - 1 || (!showVertBorder && index !== 0) ? 'none' : '1px solid' + borderColor,
  76. zIndex: index === 0 ? 20 : 0,
  77. left: index === 0 && firstLineFixed ? (enableCheck ? checkColWidth + 'rpx' : 0) : 'auto',
  78. position: index === 0 ? 'sticky' : 'static'}">
  79. <text :style="{marginLeft: autoSortShow(index) && textAlign !== 'left' ? fontSize[0] * 0.65 + 'rpx' : 0}">
  80. {{item.label || emptyString}}
  81. </text>
  82. <view class="wyb-table-header-icon" v-if="autoSortShow(index)">
  83. <text class="iconfont icon-arrow-up" :style="{
  84. color: sortWays[sortWay] === 'asc' && sortActiveKey === item.key ?
  85. headerFtColor : RGBChange(headerFtColor, 0.7, 'light'),
  86. fontWeight: 'normal',
  87. marginBottom: '-12px',
  88. transform: 'scale(0.4)'}" />
  89. <text class="iconfont icon-arrow-down" :style="{
  90. color: sortWays[sortWay] === 'inv' && sortActiveKey === item.key ?
  91. headerFtColor : RGBChange(headerFtColor, 0.7, 'light'),
  92. fontWeight: 'normal',
  93. transform: 'scale(0.4)'}" />
  94. </view>
  95. </view>
  96. </view>
  97. <view class="wyb-table-content">
  98. <view class="wyb-table-content-line" :class="'wyb-table-content-line-'+ cIndex"
  99. v-for="(content, cIndex) in contentsSort" :key="contentLineKey(content, cIndex)"
  100. :style="{borderTop: cIndex === 0 ? 'none' : '1px solid' + borderColor}">
  101. <view class="wyb-table-content-item" v-if="enableCheck" :style="{
  102. minWidth: checkColWidth + 'rpx',
  103. maxWidth: checkColWidth + 'rpx',
  104. textAlign: textAlign,
  105. justifyContent: textAlign === 'center' ? textAlign : (textAlign === 'left' ? 'flex-start' : 'flex-end'),
  106. fontSize: (fontSize[1] || fontSize[0]) + 'rpx',
  107. minHeight: (minHeight[1] || minHeight[0]) + 'rpx',
  108. padding: padding[0] + 'rpx ' + (padding[1] || padding[0]) + 'rpx',
  109. borderRight: '1px solid' + borderColor,
  110. zIndex: 21,
  111. color: contentFtColor,
  112. backgroundColor: checkerCellBgColor,
  113. left: 0,
  114. position: 'sticky'}">
  115. <view
  116. class="wyb-table-checkbox"
  117. @tap.stop="onCheckItemTap(cIndex)"
  118. :style="{
  119. width: checkColWidth * 0.5 + 'rpx',
  120. height: checkColWidth * 0.5 + 'rpx',
  121. backgroundColor: checkerBoxBgColor,
  122. border: '1px solid ' + checkerBorderColor}">
  123. <text
  124. class="iconfont icon-check"
  125. v-show="contentsSort[cIndex].checked"
  126. :style="{
  127. color: checkerColor,
  128. backgroundColor: checkerBgColor,
  129. paddingTop: (fontSize[1] || fontSize[0]) * 0.15 + 'rpx',
  130. fontSize: (fontSize[1] || fontSize[0]) + 'rpx'}" />
  131. </view>
  132. </view>
  133. <view
  134. class="wyb-table-content-item"
  135. v-for="(header, hIndex) in headers"
  136. @tap.stop="onContentItemTap(cIndex, hIndex)"
  137. :key="contentItemKey(header, hIndex)"
  138. :style="{
  139. minWidth: (header.width || defaultColWidth) + 'rpx',
  140. maxWidth: (header.width || defaultColWidth) + 'rpx',
  141. textAlign: textAlign,
  142. justifyContent: textAlign === 'center' ? textAlign : (textAlign === 'left' ? 'flex-start' : 'flex-end'),
  143. fontSize: (fontSize[1] || fontSize[0]) + 'rpx',
  144. textDecoration: autoTextDecoration(cIndex, hIndex),
  145. color: autoContentColor(cIndex, hIndex),
  146. backgroundColor: content.disabled ? '#ecf0f1' : autoContentBgColor(cIndex, hIndex),
  147. padding: padding[0] + 'rpx ' + (padding[1] || padding[0]) + 'rpx',
  148. borderBottom: cIndex === contents.length - 1 ? '1px solid' + borderColor : 'none',
  149. borderRight: hIndex === headers.length - 1 || (!showVertBorder && hIndex !== 0) ? 'none' : '1px solid' + borderColor,
  150. zIndex: hIndex === 0 ? 20 : 0,
  151. left: enableCheck ? checkColWidth + 'rpx' : 0,
  152. position: hIndex === 0 && firstLineFixed ? 'sticky' : 'static',
  153. ...content.searchStyle }">
  154. <input class="lf-w-100" placeholder="请输入.."
  155. :disabled="content.disabled || false"
  156. v-if="isObject(autoContentItem(cIndex, hIndex), 'edit')"
  157. :style="{minHeight: (minHeight[1] || minHeight[0]) + 'rpx'}"
  158. placeholder-style="font-size: 30rpx"
  159. :type="autoContentItem(cIndex, hIndex).type || 'number'"
  160. @blur="inputBlur(cIndex, hIndex, $event)"
  161. :value="autoContentItem(cIndex, hIndex).value" />
  162. <button class="diy-btn" v-else-if="isObject(autoContentItem(cIndex, hIndex), 'button')" @click="buttonClick(cIndex, hIndex)">{{ autoContentItem(cIndex, hIndex).value }}</button>
  163. <view class="lf-w-100 lf-h-100 lf-row-center" v-else :style="{minHeight: (minHeight[1] || minHeight[0]) + 'rpx', 'word-break': 'break-all'}">{{ autoContentItem(cIndex, hIndex) }}</view>
  164. </view>
  165. </view>
  166. <view v-if="computedCol.length !== 0" class="wyb-table-content-line" :style="{
  167. position: bottomComputedFixed ? 'sticky' : 'static',
  168. bottom: 0,
  169. zIndex: 25,
  170. borderTop: '1px solid' + borderColor}">
  171. <view class="wyb-table-content-item" v-if="enableCheck" :style="{
  172. minWidth: checkColWidth + 'rpx',
  173. maxWidth: checkColWidth + 'rpx',
  174. textAlign: textAlign,
  175. justifyContent: textAlign === 'center' ? textAlign : (textAlign === 'left' ? 'flex-start' : 'flex-end'),
  176. fontSize: (fontSize[1] || fontSize[0]) + 'rpx',
  177. minHeight: (minHeight[1] || minHeight[0]) + 'rpx',
  178. padding: padding[0] + 'rpx ' + (padding[1] || padding[0]) + 'rpx',
  179. borderBottom: '1px solid' + borderColor,
  180. borderRight: '1px solid' + borderColor,
  181. zIndex: 25,
  182. color: contentFtColor,
  183. backgroundColor: checkerCellBgColor,
  184. left: 0,
  185. position: 'sticky'}"></view>
  186. <view class="wyb-table-content-item" v-for="(header, index) in headers" :key="index"
  187. :style="{
  188. minWidth: (header.width || defaultColWidth) + 'rpx',
  189. maxWidth: (header.width || defaultColWidth) + 'rpx',
  190. textAlign: textAlign,
  191. justifyContent: textAlign === 'center' ? textAlign : (textAlign === 'left' ? 'flex-start' : 'flex-end'),
  192. fontSize: (fontSize[1] || fontSize[0]) + 'rpx',
  193. color: contentFtColor,
  194. minHeight: (minHeight[1] || minHeight[0]) + 'rpx',
  195. padding: padding[0] + 'rpx ' + (padding[1] || padding[0]) + 'rpx',
  196. backgroundColor: index === 0 ? firstColBgColor : contentBgColor,
  197. borderBottom: '1px solid' + borderColor,
  198. borderRight: index === headers.length - 1 || (!showVertBorder && index !== 0) ? 'none' : '1px solid' + borderColor,
  199. zIndex: index === 0 ? 20 : 0,
  200. left: enableCheck ? checkColWidth + 'rpx' : 0,
  201. position: index === 0 && firstLineFixed ? 'sticky' : 'static'}">
  202. {{autoBottomComputedItem(index)}}
  203. </view>
  204. </view>
  205. </view>
  206. </view>
  207. </view>
  208. </template>
  209. <script>
  210. import Pinyin from './js/characterToPinyin.js'
  211. import {isEqual} from './js/objEqual.js'
  212. let timer = null;
  213. export default {
  214. data() {
  215. return {
  216. bottomComputed: [],
  217. colorList: [],
  218. bgColorList: [],
  219. contentsSort: this.contents.slice(),
  220. oContentsSort: [],
  221. sortWay: 0,
  222. sortKeys: [],
  223. sortActiveKey: '',
  224. sortIsNumbers: [],
  225. checkAll: false,
  226. checkList: [],
  227. onload: true,
  228. event: {
  229. checkType: this.enableCheck,
  230. data: []
  231. },
  232. timer: null,
  233. randomClass: "x"+ Math.random().toString(32).substr(2),
  234. chars: '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
  235. }
  236. },
  237. computed: {
  238. // 加载颜色
  239. loadingColor() {
  240. let color = this.loaderColor.slice()
  241. let rgbList = this.hexToRgb(color)
  242. let top = 'rgba(' + rgbList[0] + ',' + rgbList[1] + ',' + rgbList[2] + ', 0.3)'
  243. let bottom = 'rgba(' + rgbList[0] + ',' + rgbList[1] + ',' + rgbList[2] + ', 0.3)'
  244. let right = 'rgba(' + rgbList[0] + ',' + rgbList[1] + ',' + rgbList[2] + ', 0.3)'
  245. let left = 'rgb(' + rgbList[0] + ',' + rgbList[1] + ',' + rgbList[2] + ')'
  246. return {
  247. top,
  248. bottom,
  249. right,
  250. left
  251. }
  252. },
  253. // content每一项key值
  254. contentLineKey() {
  255. return function(content, cIndex) {
  256. return this.randomString(32, this.chars)
  257. }
  258. },
  259. // 自动分配单元格内容key值
  260. contentItemKey() {
  261. return function(header, hIndex) {
  262. return this.randomString(16, this.chars);
  263. }
  264. },
  265. // 获取表格内容,每一个格子
  266. autoContentItem() {
  267. return function(cIndex, hIndex) {
  268. let content = this.contentsSort[cIndex]
  269. let header = this.headers[hIndex]
  270. let result = ''
  271. if (content[header.key] || content[header.key] === 0) {
  272. result = content[header.key]
  273. if (this.urlCol.length !== 0) {
  274. for (let i in this.urlCol) {
  275. let item = this.urlCol[i]
  276. if (header.key === item.key) {
  277. // 该单元格为链接
  278. result = content[header.key][0]
  279. }
  280. }
  281. }
  282. if (this.formatCol.length !== 0) {
  283. this.formatCol.forEach(item => {
  284. if (header.key === item.key) {
  285. let needRplace = new RegExp(`\#${item['key']}\#`, 'mg')
  286. result = item.template.replace(needRplace, result)
  287. }
  288. })
  289. }
  290. } else {
  291. result = this.emptyString
  292. }
  293. return result
  294. }
  295. },
  296. autoBottomComputedItem() {
  297. return function(index) {
  298. let bottomComputed = {}
  299. let needComputed = []
  300. this.computedCol.forEach(key => {
  301. let computedColData = []
  302. this.contentsSort.forEach(content => {
  303. computedColData.push(content[key] || '0')
  304. })
  305. needComputed.push(computedColData)
  306. })
  307. needComputed.forEach((item, index) => {
  308. let total = 0
  309. item.forEach(num => {
  310. total += parseFloat(num)
  311. })
  312. bottomComputed[this.computedCol[index]] = total
  313. })
  314. let header = this.headers[index]
  315. let result = this.computedCol.includes(header.key) ?
  316. bottomComputed[header.key] : (index === 0 ? '总计' : this.emptyString)
  317. if (this.formatCol.length !== 0) {
  318. this.formatCol.forEach(item => {
  319. if (item.bottomComputedFormat) {
  320. if (header.key === item.key) {
  321. let needRplace = new RegExp(`\#${item['key']}\#`, 'mg')
  322. result = item.template.replace(needRplace, bottomComputed[item.key])
  323. }
  324. }
  325. })
  326. }
  327. return result
  328. }
  329. },
  330. // 判断单元格为链接,显示文字下划线
  331. autoTextDecoration() {
  332. return function(cIndex, hIndex) {
  333. let result = 'auto'
  334. let content = this.contentsSort[cIndex]
  335. let header = this.headers[hIndex]
  336. if (this.urlCol.length !== 0) {
  337. for (let i in this.urlCol) {
  338. let item = this.urlCol[i]
  339. if (header.key === item.key) {
  340. // 该单元格为链接
  341. if (content[header.key]) {
  342. result = 'underline'
  343. }
  344. }
  345. }
  346. }
  347. return result
  348. }
  349. },
  350. // 分配表格内容背景颜色
  351. autoContentBgColor() {
  352. return function(cIndex, hIndex) {
  353. let result = this.contentBgColor
  354. let content = this.contentsSort[cIndex]
  355. let header = this.headers[hIndex]
  356. let keys = []
  357. // 先判断是不是首列,设置基础样式
  358. if (hIndex === 0) {
  359. result = this.firstColBgColor
  360. }
  361. // 再判断条件格式传没传值,设置条件样式
  362. if (this.valueFormat.length !== 0) {
  363. this.valueFormat.forEach(item => {
  364. keys.push(item.key)
  365. })
  366. if (keys.includes(header.key)) {
  367. // 该列开启了条件格式
  368. let key = header.key
  369. let type = this.valueFormat[keys.indexOf(key)].type
  370. let style = this.valueFormat[keys.indexOf(key)].style
  371. let range = this.valueFormat[keys.indexOf(key)].range || ''
  372. switch(type) {
  373. case 'bigger':
  374. if (parseFloat(content[key]) > range) {
  375. if (style.bgColor) result = style.bgColor
  376. }
  377. break
  378. case 'smaller':
  379. if (parseFloat(content[key]) < range) {
  380. if (style.bgColor) result = style.bgColor
  381. }
  382. break
  383. case 'equal':
  384. let val
  385. if (typeof range === 'number') val = parseFloat(content[key])
  386. else val = content[key]
  387. if (val === range) {
  388. if (style.bgColor) result = style.bgColor
  389. }
  390. break
  391. case 'range':
  392. if (parseFloat(content[key]) > range[0] && parseFloat(content[key]) < range[1]){
  393. if (style.bgColor) result = style.bgColor
  394. }
  395. break
  396. case 'average-bigger':
  397. let average = this.getAverage(key)
  398. if (parseFloat(content[key]) > average) {
  399. if (style.bgColor) result = style.bgColor
  400. }
  401. break
  402. case 'average-smaller':
  403. average = this.getAverage(key)
  404. if (parseFloat(content[key]) < average) {
  405. if (style.bgColor) result = style.bgColor
  406. }
  407. break
  408. case 'average-equal':
  409. average = this.getAverage(key)
  410. if (parseFloat(content[key]) === average) {
  411. if (style.bgColor) result = style.bgColor
  412. }
  413. break
  414. }
  415. }
  416. }
  417. // console.log("=====", content[header.key]);
  418. // return result
  419. if(this.isObject(content[header.key], 'edit')){
  420. return this.$props.contentBgColor;
  421. }else{
  422. return '#fff';
  423. }
  424. }
  425. },
  426. // 分配表格内容字体颜色
  427. autoContentColor() {
  428. return function(cIndex, hIndex) {
  429. let result = this.contentFtColor
  430. let content = this.contentsSort[cIndex]
  431. let header = this.headers[hIndex]
  432. let keys = []
  433. // 先判断是不是链接,设置基础样式
  434. if (this.urlCol.length !== 0) {
  435. for (let i in this.urlCol) {
  436. let item = this.urlCol[i]
  437. if (header.key === item.key) {
  438. // 该单元格为链接
  439. if (content[header.key]) {
  440. result = this.linkColor
  441. }
  442. }
  443. }
  444. }
  445. // 再判断条件格式传没传值,设置条件样式
  446. if (this.valueFormat.length !== 0) {
  447. this.valueFormat.forEach(item => {
  448. keys.push(item.key)
  449. })
  450. if (keys.includes(header.key)) {
  451. // 该列开启了条件格式
  452. let key = header.key
  453. let type = this.valueFormat[keys.indexOf(key)].type
  454. let style = this.valueFormat[keys.indexOf(key)].style
  455. let range = this.valueFormat[keys.indexOf(key)].range || ''
  456. switch(type) {
  457. case 'bigger':
  458. if (parseFloat(content[key]) > range) {
  459. if (style.color) result = style.color
  460. }
  461. break
  462. case 'smaller':
  463. if (parseFloat(content[key]) < range) {
  464. if (style.color) result = style.color
  465. }
  466. break
  467. case 'equal':
  468. let val
  469. if (typeof range === 'number') val = parseFloat(content[key])
  470. else val = content[key]
  471. if (val === range) {
  472. if (style.color) result = style.color
  473. }
  474. break
  475. case 'range':
  476. if (parseFloat(content[key]) > range[0] && parseFloat(content[key]) < range[1]){
  477. if (style.color) result = style.color
  478. }
  479. break
  480. case 'average-bigger':
  481. let average = this.getAverage(key)
  482. if (parseFloat(content[key]) > average) {
  483. if (style.color) result = style.color
  484. }
  485. break
  486. case 'average-smaller':
  487. average = this.getAverage(key)
  488. if (parseFloat(content[key]) < average) {
  489. if (style.color) result = style.color
  490. }
  491. break
  492. case 'average-equal':
  493. average = this.getAverage(key)
  494. if (parseFloat(content[key]) === average) {
  495. if (style.color) result = style.color
  496. }
  497. break
  498. }
  499. }
  500. }
  501. return result
  502. }
  503. },
  504. // 显示筛选排序
  505. autoSortShow() {
  506. return function(hIndex) {
  507. let result = false
  508. let header = this.headers[hIndex]
  509. let keys = []
  510. // 判断排序是否传值
  511. if (this.sortCol.length !== 0 && this.sortKeys.length === 0) {
  512. this.sortCol.forEach(item => {
  513. keys.push(item.key)
  514. })
  515. this.sortKeys = keys
  516. if (keys.includes(header.key)) {
  517. result = true
  518. }
  519. } else if (this.sortCol.length !== 0) {
  520. if (this.sortKeys.includes(header.key)) {
  521. result = true
  522. }
  523. }
  524. return result
  525. }
  526. },
  527. // 获取屏幕可用宽度
  528. screenWidth() {
  529. return `${uni.getSystemInfoSync()['screenWidth']}px`
  530. }
  531. },
  532. props: {
  533. headers: {
  534. type: Array,
  535. default() {
  536. return [{
  537. key: 'name',
  538. label: '姓名'
  539. },{
  540. key: 'age',
  541. label: '年龄'
  542. },{
  543. key: 'sex',
  544. label: '性别'
  545. },{
  546. key: 'height',
  547. label: '身高'
  548. },{
  549. key: 'info',
  550. label: '描述'
  551. },{
  552. key: 'operation',
  553. label: '操作'
  554. }]
  555. }
  556. },
  557. contents: {
  558. type: Array,
  559. default() {
  560. return [{
  561. name: '张三',
  562. age: '18',
  563. sex: '男',
  564. height: '192cm',
  565. info: '无敌叫是',
  566. operation: {button: true, key: 'delete', value: '删除'}
  567. }, {
  568. name: '李四',
  569. age: '18',
  570. sex: '男',
  571. height: '192cm',
  572. info: '无敌叫是'
  573. }, {
  574. name: '赵五',
  575. age: '18',
  576. sex: '男',
  577. height: '192cm',
  578. info: '无敌叫是'
  579. }]
  580. }
  581. },
  582. emptyString: {
  583. type: String,
  584. default: '-'
  585. },
  586. width: {
  587. type: String,
  588. default: `${uni.getSystemInfoSync().screenWidth}px`
  589. },
  590. height: {
  591. type: String,
  592. default: 'auto'
  593. },
  594. fontSize: {
  595. type: Array,
  596. default() {
  597. return [30]
  598. }
  599. },
  600. defaultColWidth: {
  601. type: Number,
  602. default: 165
  603. },
  604. headerWeight: {
  605. type: Boolean,
  606. default: true
  607. },
  608. minHeight: {
  609. type: Array,
  610. default() {
  611. return [70]
  612. }
  613. },
  614. headerBgColor: {
  615. type: String,
  616. default: '#fff'
  617. },
  618. contentBgColor: {
  619. type: String,
  620. default: '#fff'
  621. },
  622. headerFtColor: {
  623. type: String,
  624. default: '#3e3e3e'
  625. },
  626. contentFtColor: {
  627. type: String,
  628. default: '#3e3e3e'
  629. },
  630. linkColor: {
  631. type: String,
  632. default: '#0024c8'
  633. },
  634. firstColBgColor: {
  635. type: String,
  636. default: '#f1f1f1'
  637. },
  638. firstLineFixed: {
  639. type: Boolean,
  640. default: false
  641. },
  642. textAlign: {
  643. type: String,
  644. default: 'center'
  645. },
  646. padding: {
  647. type: Array,
  648. default() {
  649. return [5, 10]
  650. }
  651. },
  652. borderColor: {
  653. type: String,
  654. default: '#e1e1e1'
  655. },
  656. urlCol: {
  657. type: Array,
  658. default() {
  659. return []
  660. }
  661. },
  662. computedCol: {
  663. type: Array,
  664. default() {
  665. return []
  666. }
  667. },
  668. bottomComputedFixed: {
  669. type: Boolean,
  670. default: true
  671. },
  672. valueFormat: {
  673. type: Array,
  674. default() {
  675. return []
  676. }
  677. },
  678. formatCol: {
  679. type: Array,
  680. default() {
  681. return []
  682. }
  683. },
  684. showLeftAndRightBorder: {
  685. type: Boolean,
  686. default: false
  687. },
  688. showVertBorder: {
  689. type: Boolean,
  690. default: true
  691. },
  692. sortCol: {
  693. type: Array,
  694. default() {
  695. return []
  696. }
  697. },
  698. sortWays: {
  699. type: Array,
  700. default() {
  701. return ['none', 'asc', 'inv']
  702. }
  703. },
  704. loading: {
  705. type: Boolean,
  706. default: false
  707. },
  708. loaderSize: {
  709. type: [String, Number],
  710. default: 50
  711. },
  712. loaderColor: {
  713. type: String,
  714. default: '#a3a3a3'
  715. },
  716. loaderBgColor: {
  717. type: String,
  718. default: '#f8f8f8'
  719. },
  720. enableCheck: {
  721. type: String,
  722. default: ''
  723. },
  724. checkColWidth: {
  725. type: [String, Number],
  726. default: '70'
  727. },
  728. checkerColor: {
  729. type: String,
  730. default: '#3e3e3e'
  731. },
  732. checkerBorderColor: {
  733. type: String,
  734. default: '#d3d3d3'
  735. },
  736. checkerBgColor: {
  737. type: String,
  738. default: 'rgba(0, 0, 0, 0)'
  739. },
  740. checkerBoxBgColor: {
  741. type: String,
  742. default: 'rgba(0, 0, 0, 0)'
  743. },
  744. checkerCellBgColor: {
  745. type: String,
  746. default: '#f1f1f1'
  747. },
  748. scrollToInput: {
  749. type: Boolean, // 是否自动滚动到含有input的位置
  750. default: false
  751. },
  752. searchKey: {
  753. type: String,
  754. default: '' // 表格搜索内容
  755. }
  756. },
  757. watch: {
  758. headers(val) {
  759. this.$forceUpdate()
  760. },
  761. contents(val) {
  762. this.contentsSort = val.slice()
  763. if (this.onload) {
  764. this.contentsSort.forEach(item => {
  765. this.$set(item, 'checked', false)
  766. })
  767. this.oContentsSort = this.contentsSort.slice()
  768. this.onload = false
  769. }
  770. this.$forceUpdate()
  771. },
  772. searchKey(val){
  773. console.log("搜索:", val);
  774. let positionIndex = null;
  775. // 处理被模糊匹配的item背景颜色变化
  776. this.contentsSort.map((c_item, c_index) => {
  777. this.$set(c_item, 'searchStyle', {});
  778. this.headers.map(h_item => {
  779. if(h_item.search){
  780. let contentLine = c_item[h_item.key];
  781. if(val && contentLine.indexOf(val) >= 0){
  782. if(positionIndex == null){
  783. positionIndex = c_index; // 拿到第一个被匹配的table item
  784. }
  785. this.$set(c_item, 'searchStyle', {backgroundColor: '#ffdede'});
  786. }
  787. }
  788. })
  789. })
  790. // 处理匹配到item时滚动至第一个item
  791. if(positionIndex != null){
  792. let tabView = document.querySelector('.wyb-table-scroll-view'); // 获取第一层大对象,table
  793. let tabContent = tabView.querySelector('.wyb-table-content'); // 获取第二层,table的content部分
  794. let contentLine = tabContent.querySelector('.wyb-table-content-line-'+ positionIndex); // 获取第三层,获取内容列
  795. let tabViewTop = tabView.offsetTop; // table距离页面高度
  796. let offsetHeight = contentLine.offsetHeight; // table行高度
  797. let offsetTop = contentLine.offsetTop; // table行距离页面高度
  798. tabView.scrollTop = offsetTop - tabViewTop - offsetHeight;
  799. }else if(val){
  800. this.$msg('该关键字没有搜索到物资哦')
  801. }
  802. }
  803. },
  804. mounted() {
  805. this.contentsSort.forEach(item => {
  806. this.$set(item, 'checked', false)
  807. })
  808. this.oContentsSort = this.contentsSort.slice()
  809. if (this.sortCol.length !== 0) {
  810. this.sortActiveKey = this.sortCol[0].key
  811. uni.setStorageSync('lastSortActiveKey', this.sortActiveKey)
  812. this.doSort(this.sortCol[0].key, this.sortWays[this.sortWay], this.sortCol[0].isNumber)
  813. }
  814. this.autoScrollView();
  815. },
  816. destroyed(){
  817. if(this.timer){
  818. clearInterval(this.timer);
  819. this.timer = null;
  820. }
  821. },
  822. methods: {
  823. // 页面初次加载,判断是否自动滚动至input输入框所在的位置
  824. autoScrollView(){
  825. try{
  826. // #ifdef H5
  827. let that = this;
  828. if(that.$props.scrollToInput){
  829. setTimeout(function(){
  830. // 完整选取dom .wyb-table-scroll-view .wyb-table-content .wyb-table-content-line .wyb-table-content-item input
  831. let tabView = document.querySelector('.'+ that.randomClass +' .wyb-table-scroll-view'); // 获取第一层大对象,table
  832. let tabContent = tabView.querySelector('.wyb-table-content'); // 获取第二层,table的content部分
  833. let contentLine = tabContent.querySelector('.wyb-table-content-line'); // 获取第三层,获取内容列
  834. let contentItem = contentLine.querySelector('.wyb-table-content-item input'); // 获取第四层,内容单元格
  835. let offsetWidth = contentItem.offsetParent.offsetWidth;
  836. let offsetLeft = contentItem.offsetParent.offsetLeft;
  837. let scrollLeft = offsetLeft - offsetWidth - 20;
  838. if(that.timer){
  839. clearInterval(that.timer);
  840. that.timer = null;
  841. }
  842. if(scrollLeft <= 0){
  843. return; // 判断含有input的元素是否在前面,是就不做滚动
  844. }
  845. that.timer = setInterval(function(){
  846. let currentScrollLeft = tabView.scrollLeft;
  847. tabView.scrollLeft += 2;
  848. if((tabView.scrollLeft >= scrollLeft) || currentScrollLeft == tabView.scrollLeft){
  849. clearInterval(that.timer); // 判断相等是为了元素已经滚动到后面没法滚动了,scrollLeft赋值失败,导致计时器一直在跑
  850. that.timer = null;
  851. }
  852. }, 2);
  853. }, 1000)
  854. }
  855. // #endif
  856. }catch(e){
  857. console.log(e);
  858. }
  859. },
  860. inputBlur(cIndex, hIndex, event){
  861. let event_obj = {};
  862. let value = event.detail.value;
  863. let content = this.contentsSort[cIndex];
  864. let header = this.headers[hIndex];
  865. if(content[header.key]){
  866. event_obj = {
  867. content: content[header.key],
  868. contentIndex: cIndex,
  869. header: header.label,
  870. headerIndex: hIndex,
  871. key: header.key,
  872. lineData: content,
  873. detailValue: value
  874. }
  875. this.$emit('onInputChange', event_obj);
  876. }
  877. },
  878. buttonClick(cIndex, hIndex){
  879. let event_obj = {};
  880. let content = this.contentsSort[cIndex];
  881. let header = this.headers[hIndex];
  882. if(content[header.key]){
  883. event_obj = {
  884. content: content[header.key],
  885. contentIndex: cIndex,
  886. header: header.label,
  887. headerIndex: hIndex,
  888. key: header.key,
  889. lineData: content
  890. }
  891. this.$emit('onButtonClick', event_obj);
  892. }
  893. },
  894. isObject(val, key){
  895. return this.$shared.isValueType(val) == 'object' && val[key];
  896. },
  897. doSort(key, type, isNumber) {
  898. let arr = this.contentsSort
  899. if (type === 'asc') {
  900. // 升序
  901. if (isNumber) {
  902. arr.sort((a, b) => {
  903. return (parseFloat(a[key].toString().replace(/[^0-9]/ig, "")) || 0) -
  904. (parseFloat(b[key].toString().replace(/[^0-9]/ig, "")) || 0)
  905. })
  906. } else {
  907. arr.sort((a, b) => {
  908. let A = Pinyin.getSpell(a[key].charAt(0), function(charactor, spell) {
  909. return spell[1]
  910. }).charAt(0).charCodeAt()
  911. let B = Pinyin.getSpell(b[key].charAt(0), function(charactor, spell) {
  912. return spell[1]
  913. }).charAt(0).charCodeAt()
  914. return A - B
  915. })
  916. }
  917. } else if (type === 'inv') {
  918. // 倒序
  919. if (isNumber) {
  920. arr.sort((a, b) => {
  921. return (parseFloat(b[key].toString().replace(/[^0-9]/ig, "")) || 0) -
  922. (parseFloat(a[key].toString().replace(/[^0-9]/ig, "")) || 0)
  923. })
  924. } else {
  925. arr.sort((a, b) => {
  926. let A = Pinyin.getSpell(a[key].charAt(0), function(charactor, spell) {
  927. return spell[1]
  928. }).charAt(0).charCodeAt()
  929. let B = Pinyin.getSpell(b[key].charAt(0), function(charactor, spell) {
  930. return spell[1]
  931. }).charAt(0).charCodeAt()
  932. return B - A
  933. })
  934. }
  935. } else {
  936. this.contentsSort = this.oContentsSort.slice()
  937. }
  938. if (this.enableCheck) {
  939. this.event.data.forEach(item => {
  940. this.contentsSort.forEach((content, index) => {
  941. if (isEqual(item.lineData, content)) {
  942. item.index = index
  943. }
  944. })
  945. })
  946. }
  947. this.$forceUpdate()
  948. },
  949. initBottomComputed() {
  950. let result = {}
  951. let needComputed = []
  952. this.computedCol.forEach(key => {
  953. let computedColData = []
  954. this.contentsSort.forEach(content => {
  955. computedColData.push(content[key] || '0')
  956. })
  957. needComputed.push(computedColData)
  958. })
  959. needComputed.forEach((item, index) => {
  960. let total = 0
  961. item.forEach(num => {
  962. total += parseFloat(num)
  963. })
  964. result[this.computedCol[index]] = total
  965. })
  966. this.bottomComputed = result
  967. },
  968. onHeaderItemTap(index) {
  969. let header = this.headers[index]
  970. const lastSortActiveKey = uni.getStorageSync('lastSortActiveKey') || ''
  971. if (this.sortCol.length !== 0) {
  972. if (this.sortKeys.includes(header.key)) {
  973. // 当前列开启了排序
  974. this.sortActiveKey = header.key
  975. uni.setStorageSync('lastSortActiveKey', this.sortActiveKey)
  976. if (this.sortWay < 2 && lastSortActiveKey === this.sortActiveKey) {
  977. this.sortWay++
  978. } else if (lastSortActiveKey !== this.sortActiveKey) {
  979. this.sortWay = 1
  980. } else if (this.sortWay >= 2) {
  981. this.sortWay = 0
  982. }
  983. let isNumber = this.sortCol[this.sortKeys.indexOf(header.key)].isNumber
  984. this.doSort(header.key, this.sortWays[this.sortWay], isNumber)
  985. }
  986. }
  987. },
  988. onContentItemTap(cIndex, hIndex) {
  989. let event = {}
  990. let content = this.contentsSort[cIndex]
  991. let header = this.headers[hIndex]
  992. let keys = []
  993. if (this.urlCol.length !== 0) {
  994. for (let i in this.urlCol) {
  995. let item = this.urlCol[i]
  996. keys.push(item.key)
  997. }
  998. }
  999. if (content && content[header.key]) {
  1000. if (keys.includes(header.key)) {
  1001. // 该单元格为链接
  1002. switch(this.urlCol[keys.indexOf(header.key)].type) {
  1003. case 'route':
  1004. let url = content[header.key][1]
  1005. if (content[header.key][2]) {
  1006. url = `${url}?`
  1007. Object.keys(content[header.key][2]).forEach(key => {
  1008. url += `&${key}=${content[header['key']][2][key]}`
  1009. })
  1010. }
  1011. uni.navigateTo({url})
  1012. break
  1013. case 'http':
  1014. this.openURL(content[header.key][1])
  1015. break
  1016. }
  1017. } else {
  1018. event = {
  1019. content: content[header.key],
  1020. contentIndex: cIndex,
  1021. header: header.label,
  1022. headerIndex: hIndex,
  1023. key: header.key,
  1024. lineData: content
  1025. }
  1026. this.$emit('onCellClick', event)
  1027. }
  1028. } else {
  1029. event = {
  1030. content: '',
  1031. contentIndex: cIndex,
  1032. header: header.label,
  1033. headerIndex: hIndex,
  1034. key: header.key,
  1035. lineData: content
  1036. }
  1037. if (keys.includes(header.key)) {
  1038. // 该单元格为链接
  1039. event['isLink'] = true
  1040. }
  1041. this.$emit('onCellClick', event)
  1042. }
  1043. },
  1044. onCheckAllTap() {
  1045. if (this.enableCheck === 'multiple') {
  1046. let checkList = []
  1047. this.contentsSort.forEach(item => {
  1048. checkList.push(item.checked)
  1049. })
  1050. this.checkList = checkList
  1051. if (!this.checkAll) {
  1052. this.checkAll = true
  1053. this.contentsSort.forEach(item => {
  1054. item.checked = true
  1055. })
  1056. this.event.data = []
  1057. this.contentsSort.forEach((content, index) => {
  1058. this.event.data.push({
  1059. index,
  1060. lineData: content
  1061. })
  1062. })
  1063. } else {
  1064. this.checkAll = false
  1065. this.event.data = []
  1066. this.contentsSort.forEach(item => {
  1067. item.checked = false
  1068. })
  1069. }
  1070. this.$emit('onCheck', this.event)
  1071. }
  1072. },
  1073. onCheckItemTap(cIndex) {
  1074. let content = this.contentsSort[cIndex]
  1075. if (this.enableCheck === 'single') {
  1076. this.contentsSort.forEach((item, index) => {
  1077. if (cIndex === index) {
  1078. item.checked = !item.checked
  1079. } else {
  1080. item.checked = false
  1081. }
  1082. })
  1083. } else if (this.enableCheck === 'multiple') {
  1084. this.contentsSort[cIndex]['checked'] = !this.contentsSort[cIndex]['checked']
  1085. }
  1086. if (this.contentsSort[cIndex]['checked']) {
  1087. if (this.enableCheck === 'single') {
  1088. this.event.data = []
  1089. }
  1090. this.event.data.push({
  1091. index: cIndex,
  1092. lineData: this.contentsSort[cIndex]
  1093. })
  1094. } else {
  1095. this.event.data.forEach(item => {
  1096. if (item.index === cIndex) this.event.data.splice(this.event.data.indexOf(item), 1)
  1097. })
  1098. if (this.event.data.length === 0) {
  1099. this.checkAll = false
  1100. }
  1101. }
  1102. this.$forceUpdate()
  1103. this.$emit('onCheck', this.event)
  1104. },
  1105. openURL(href) {
  1106. // #ifdef APP-PLUS
  1107. plus.runtime.openURL(href)
  1108. // #endif
  1109. // #ifdef H5
  1110. window.open(href)
  1111. // #endif
  1112. // #ifdef MP
  1113. uni.setClipboardData({
  1114. data: href,
  1115. success() {
  1116. uni.showToast({
  1117. title: '网址已复制,请在手机浏览器里粘贴该网址',
  1118. icon: 'none'
  1119. })
  1120. }
  1121. })
  1122. // #endif
  1123. },
  1124. getAverage(key) {
  1125. let numList = []
  1126. this.contentsSort.forEach(content => {
  1127. numList.push(parseFloat(content[key]) || 0)
  1128. })
  1129. return numList.reduce((a, b) => a + b) / numList.length
  1130. },
  1131. getTotal(key) {
  1132. let numList = []
  1133. this.contentsSort.forEach(content => {
  1134. numList.push(parseFloat(content[key]) || 0)
  1135. })
  1136. return numList.reduce((a, b) => a + b)
  1137. },
  1138. RGBChange(color, level, type) {
  1139. // 判断颜色类型
  1140. let r = 0,
  1141. g = 0,
  1142. b = 0,
  1143. hasAlpha = false,
  1144. alpha = 1
  1145. if (color.indexOf('#') !== -1) {
  1146. // hex转rgb
  1147. if (color.length === 4) {
  1148. let arr = color.split('')
  1149. color = '#' + arr[1] + arr[1] + arr[2] + arr[2] + arr[3] + arr[3]
  1150. }
  1151. let color16List = [color.substring(1, 3), color.substring(3, 5), color.substring(5, 7)]
  1152. r = parseInt(color16List[0], 16)
  1153. g = parseInt(color16List[1], 16)
  1154. b = parseInt(color16List[2], 16)
  1155. } else {
  1156. hasAlpha = color.indexOf('a') !== -1
  1157. let root = color.slice()
  1158. let idx = root.indexOf('(') + 1
  1159. root = root.substring(idx)
  1160. let firstDotIdx = root.indexOf(',')
  1161. r = parseFloat(root.substring(0, firstDotIdx))
  1162. root = root.substring(firstDotIdx + 1)
  1163. let secondDotIdx = root.indexOf(',')
  1164. g = parseFloat(root.substring(0, secondDotIdx))
  1165. root = root.substring(secondDotIdx + 1)
  1166. if (hasAlpha) {
  1167. let thirdDotIdx = root.indexOf(',')
  1168. b = parseFloat(root.substring(0, thirdDotIdx))
  1169. alpha = parseFloat(root.substring(thirdDotIdx + 1))
  1170. } else {
  1171. b = parseFloat(root)
  1172. }
  1173. }
  1174. let rgbc = [r, g, b]
  1175. // 减淡或加深
  1176. for (var i = 0; i < 3; i++)
  1177. type === 'light' ? rgbc[i] = Math.floor((255 - rgbc[i]) * level + rgbc[i]) : rgbc[i] = Math.floor(rgbc[i] * (1 -
  1178. level))
  1179. if (hasAlpha) {
  1180. return `rgba(${rgbc[0]}, ${rgbc[1]}, ${rgbc[2]}, ${alpha})`
  1181. } else {
  1182. return `rgb(${rgbc[0]}, ${rgbc[1]}, ${rgbc[2]})`
  1183. }
  1184. },
  1185. hexToRgb(color) {
  1186. if (color.length === 4) {
  1187. let arr = color.split('')
  1188. color = '#' + arr[1] + arr[1] + arr[2] + arr[2] + arr[3] + arr[3]
  1189. }
  1190. let color16List = [color.substring(1, 3), color.substring(3, 5), color.substring(5, 7)]
  1191. let r = parseInt(color16List[0], 16)
  1192. let g = parseInt(color16List[1], 16)
  1193. let b = parseInt(color16List[2], 16)
  1194. return [r, g, b]
  1195. },
  1196. randomString(length, chars) {
  1197. var result = ''
  1198. for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)]
  1199. return result
  1200. }
  1201. },
  1202. destroyed(){
  1203. if(timer){
  1204. clearInterval(timer);
  1205. timer = null;
  1206. }
  1207. }
  1208. }
  1209. </script>
  1210. <style>
  1211. @import './css/iconfont.css';
  1212. @import './css/loader.css';
  1213. .ios-header-bug {
  1214. height: 0;
  1215. width: 1px;
  1216. opacity: 0;
  1217. }
  1218. .wyb-table-scroll-view {
  1219. overflow: scroll;
  1220. -webkit-overflow-scrolling: touch;
  1221. border: 2rpx solid #e5e5e5;
  1222. }
  1223. .wyb-table-scroll-view::-webkit-scrollbar {
  1224. display: none;
  1225. /* #ifdef MP-WEIXIN */
  1226. width: 0;
  1227. height: 0;
  1228. /* #endif */
  1229. }
  1230. .wyb-table-loading-box {
  1231. display: flex;
  1232. align-items: center;
  1233. justify-content: center;
  1234. z-index: 500;
  1235. }
  1236. .wyb-table-header {
  1237. position: sticky;
  1238. top: 0;
  1239. display: grid;
  1240. grid-auto-flow: column;
  1241. width: max-content;
  1242. z-index: 25;
  1243. }
  1244. .wyb-table-header-item {
  1245. flex: 1;
  1246. display: flex;
  1247. align-items: center;
  1248. box-sizing: border-box;
  1249. position: relative;
  1250. }
  1251. .wyb-table-header-icon {
  1252. display: flex;
  1253. flex-direction: column;
  1254. }
  1255. .wyb-table-content-line {
  1256. display: grid;
  1257. grid-auto-flow: column;
  1258. width: max-content;
  1259. position: relative;
  1260. }
  1261. .wyb-table-content-item {
  1262. display: flex;
  1263. flex-direction: row;
  1264. align-items: center;
  1265. box-sizing: border-box;
  1266. }
  1267. .wyb-table-checkbox {
  1268. border-radius: 3px;
  1269. display: flex;
  1270. align-items: center;
  1271. justify-content: center;
  1272. position: relative;
  1273. }
  1274. .icon-check {
  1275. width: 100%;
  1276. height: 100%;
  1277. position: absolute;
  1278. border-radius: 0;
  1279. border-radius: 3px;
  1280. font-weight: bold;
  1281. box-sizing: border-box;
  1282. transform: scale(1.1);
  1283. }
  1284. .diy-btn{
  1285. background-color: transparent;
  1286. color: #FF3838;
  1287. height: max-content;
  1288. line-height: initial;
  1289. font-size: 28rpx;
  1290. }
  1291. </style>