MixedSheet.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. <script setup>
  2. // import Sheet from '@/components/Sheet.vue'
  3. import { ref, reactive, computed, onMounted, watch } from 'vue'
  4. const props = defineProps({
  5. TableInfo:{
  6. type:Object,
  7. default:{}
  8. }
  9. })
  10. const tableHeight = ref(0)
  11. const disabled = ref(true)
  12. const cellRef = ref([])
  13. const tableRef = ref(null);
  14. const tableData = reactive(props.TableInfo.TableData.Data);
  15. const freezeData = reactive(props.TableInfo.ExtraConfig.TableFreeze);
  16. const columnHeader = computed(() => {
  17. return getColumnHeaderCode(
  18. props.TableInfo.TableData.Data[0] ? props.TableInfo.TableData.Data[0].length : 0
  19. );
  20. });
  21. const rowHeader = computed(() => {
  22. let total_length = props.TableInfo.TableData.Data.length;
  23. return getRowHeaderCode(total_length);
  24. });
  25. const minRow = computed(() => {
  26. return Math.min(props.TableInfo.ExtraConfig.TableFreeze.FreezeStartRow, props.TableInfo.ExtraConfig.TableFreeze.FreezeEndRow)
  27. });
  28. const maxRow = computed(() => {
  29. return Math.max(props.TableInfo.ExtraConfig.TableFreeze.FreezeStartRow, props.TableInfo.ExtraConfig.TableFreeze.FreezeEndRow)
  30. });
  31. const maxCol = computed(() => {
  32. return Math.max(props.TableInfo.ExtraConfig.TableFreeze.FreezeStartCol, props.TableInfo.ExtraConfig.TableFreeze.FreezeEndCol)
  33. });
  34. const minCol = computed(() => {
  35. return Math.min(props.TableInfo.ExtraConfig.TableFreeze.FreezeStartCol, props.TableInfo.ExtraConfig.TableFreeze.FreezeEndCol)
  36. });
  37. // watch(
  38. // () => props.TableInfo.TableData.Data, // 假设 config.data 是 props.TableInfo 下的一个属性
  39. // (newVal) => {
  40. // nextTick(() => {
  41. // tableHeight.value = tableRef.value ? tableRef.value.offsetHeight : 35;
  42. // });
  43. // },
  44. // { deep: true }
  45. // );
  46. //手机端pc端不同样式
  47. const dynamicSty = computed(()=>{
  48. return isMobile() ? 'mobile-sty' : 'pc-sty';
  49. })
  50. //判断是否是手机设备
  51. function isMobile() {
  52. // 判断是否是移动设备的正则表达式
  53. const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
  54. // 获取用户代理信息
  55. const userAgent = navigator.userAgent;
  56. // 使用正则表达式检查用户代理信息
  57. return mobileRegex.test(userAgent);
  58. }
  59. // 字母列标
  60. function getColumnHeaderCode(len) {
  61. let tag_arr = [];
  62. for(let i=0;i<len;i++) tag_arr.push(String.fromCharCode(65+i));
  63. return tag_arr;
  64. }
  65. // 行标
  66. function getRowHeaderCode(len) {
  67. let tag_arr = [];
  68. for(let i=0;i<len;i++) tag_arr.push(String(1+i));
  69. return tag_arr;
  70. }
  71. // 判断展示小数位数值还是原来的值
  72. function showCellValue(cell){
  73. // console.log(cell)
  74. let Value=''
  75. if("Decimal" in cell&&cell.Decimal!=-1){
  76. const multiplier = Math.pow(10, cell.Decimal);
  77. const cellValue=+cell.Value
  78. Value= cell.Decimal == 0 ? Math.round(cellValue) : Math.round(cellValue * multiplier) / multiplier;
  79. }else{
  80. Value=cell.ShowValue
  81. }
  82. return Value
  83. }
  84. // 初始化单元格横纵坐标
  85. function initIndex(rindex, cindex, row, col) {
  86. props.TableInfo.TableData.Data[rindex][cindex]["cIndex"] = cindex;
  87. props.TableInfo.TableData.Data[rindex][cindex]["rIndex"] = rindex;
  88. props.TableInfo.TableData.Data[rindex][cindex]["colIndex"] = col;
  89. props.TableInfo.TableData.Data[rindex][cindex]["rowIndex"] = row;
  90. }
  91. onMounted(() => {
  92. tableHeight.value = tableRef.value ? tableRef.value.offsetHeight : 35;
  93. })
  94. // 设置背景色及字体颜色
  95. function getShowCss(style, type = '') {
  96. const styleCss = JSON.parse(style);
  97. let color = styleCss.glObj ? styleCss.glObj["color"] : styleCss["color"];
  98. let BackgroundColor = styleCss.glObj
  99. ? styleCss.glObj["background-color"]
  100. : styleCss["background-color"];
  101. let obj = {
  102. color: color,
  103. "background-color": BackgroundColor,
  104. 'text-align': styleCss.align ? styleCss.align : 'center',
  105. 'width': styleCss.width ? styleCss.width + 'px' : '140px',
  106. }
  107. if (type == 'header') {
  108. obj = {
  109. 'width': styleCss.width ? styleCss.width + 'px' : '140px',
  110. }
  111. }
  112. return obj;
  113. }
  114. // 是否展示小数
  115. function isShowDecimal(cell) {
  116. // // console.log(cell,111)
  117. const styleCss = cell.ShowStyle ? JSON.parse(cell.ShowStyle) : {};
  118. let tag = !isNaN(parseFloat(cell.ShowValue)) && isFinite(cell.ShowValue)
  119. return tag ? !isNaN(parseFloat(styleCss.decimal)) && isFinite(styleCss.decimal) : false
  120. }
  121. // 由于在showStyle做了背景色及字体颜色处理,解决判断冲突
  122. function isShowFormat(style, type = "css") {
  123. const styleCss = style ? JSON.parse(style) : {};
  124. let tag =
  125. type === "css"
  126. ? styleCss.pn > 0 || styleCss.pn < 0 || ["percent"].includes(styleCss.nt)
  127. : style.EdbInfoId;
  128. return tag;
  129. }
  130. // 展示小数
  131. function showDecimalValue(cell) {
  132. const styleCss = cell.ShowStyle ? JSON.parse(cell.ShowStyle) : {};
  133. let Value = ''
  134. if (styleCss.nt == 'percent' && cell.ShowFormatValue.indexOf('%') > -1) {
  135. if (styleCss.last == 'nt') {
  136. Value = commonDecimalValue(cell.ShowValue * 100, styleCss.decimal) + '%'
  137. } else {
  138. Value = (parseFloat(commonDecimalValue(cell.ShowValue, styleCss.decimal) * 100)) + '%'
  139. }
  140. } else {
  141. Value = parseFloat(commonDecimalValue(cell.ShowValue, styleCss.decimal)).toFixed(styleCss.decimal)
  142. }
  143. if (getDecimalPlaces(cell.ShowValue) < styleCss.decimal || styleCss.nt == 'percent') {
  144. Value = transDecimalPlace(cell.ShowValue + '', { ...styleCss, pn: styleCss.decimal - getDecimalPlaces(cell.ShowValue) + (styleCss.nt == 'percent'&&getDecimalPlaces(cell.ShowValue)!=0 ? 2 : 0) })
  145. }
  146. // console.log(cell)
  147. tableData[cell.rIndex][cell.cIndex].ShowFormatValue = Value;
  148. return Value
  149. }
  150. function commonDecimalValue(values, decimal) {
  151. const multiplier = Math.pow(10, decimal);
  152. return decimal == 0 ? Math.round(values) : Math.round(values * multiplier) / multiplier;
  153. }
  154. /* 计算小数点位数 */
  155. function getDecimalPlaces(numStr) {
  156. // 移除百分号,如果有的话
  157. numStr = numStr.replace('%', '');
  158. // 如果没有小数点,说明小数点位数为 0
  159. if (!numStr.includes('.')) {
  160. return 0;
  161. }
  162. // 获取当前小数点后的位数
  163. const decimalPlaces = numStr.split('.')[1].length;
  164. return decimalPlaces;
  165. }
  166. /* 增加减少小数点位数 */
  167. function transDecimalPlace(str,{pn,nt}) {
  168. if(!isNumberVal(str)) return '';
  169. let s = str.replace(/%/,''),
  170. decimalPlaces = getDecimalPlaces(str),
  171. decimalNum=pn;
  172. //是否是原数字后设置百分比的
  173. let transPercent = (nt==='percent'&&!str.endsWith('%')) ? true : false;
  174. //原百分比设置为数字的
  175. let transDecimal = (nt==='number'&&str.endsWith('%')) ? true : false
  176. //后缀 百分号
  177. let suffix = ((str.endsWith('%')&&nt!=='number')||transPercent) ? '%' : '';
  178. let num = parseFloat(s);
  179. if(decimalPlaces===0) { //整数
  180. if(transPercent) {
  181. return decimalNum > 0
  182. ? `${multiply(num,100)}.${'0'.repeat(decimalNum)}${suffix}`
  183. : `${multiply(num,100)}${suffix}`;
  184. }
  185. if(transDecimal) {
  186. return decimalNum > 0
  187. ? `${divide(num,100)}${'0'.repeat(decimalNum)}`
  188. : `${divide(num,100)}`;
  189. }
  190. // 补零
  191. return decimalNum > 0
  192. ? `${s}.${'0'.repeat(decimalNum)}${suffix}`
  193. : `${s}${suffix}`;
  194. }
  195. if(decimalNum > 0) {
  196. let addPointStr = `${s}${'0'.repeat(decimalNum)}`;
  197. return transPercent
  198. ? `${parseFloat(multiply(num,100)).toFixed(decimalPlaces+decimalNum-2)}${suffix}`
  199. : transDecimal
  200. ? `${parseFloat(divide(num,100)).toFixed(decimalPlaces+decimalNum+2)}`
  201. : `${addPointStr}${suffix}`
  202. }else {
  203. let maxDecimal = Math.max(0,decimalNum+decimalPlaces);
  204. let maxDecimalPercent = Math.max(0,maxDecimal-2);
  205. return transPercent
  206. ? `${parseFloat(multiply(num,100)).toFixed(maxDecimalPercent)}${suffix}`
  207. : transDecimal
  208. ? `${parseFloat(divide(num,100)).toFixed(maxDecimal+2)}`
  209. : `${parseFloat(num.toFixed(maxDecimal))}${suffix}`
  210. }
  211. }
  212. /* 判断值是否是一个数字或数字字符串 */
  213. function isNumberVal(value) {
  214. let reg = /^[-]?(?:\d*\.?\d+|\d+%|\d*\.\d+%)$/;
  215. return reg.test(value);
  216. }
  217. // 是否固定列
  218. function isWithinColRange (index) {
  219. return rowHeader.value[index] >= minCol.value && rowHeader.value[index] <= maxCol.value
  220. }
  221. // 获取某一列的宽度
  222. function getColumnHeaderWidth (index) {
  223. return cellRef.value && cellRef.value ? cellRef.value.offsetWidth : 104
  224. }
  225. </script>
  226. <template>
  227. <div class="sheet-show-wrapper">
  228. <div :class="['table-wrapper',dynamicSty ]" >
  229. <table
  230. border="0"
  231. class="table el-table__body"
  232. id="myTable"
  233. :style="disabled ? 'width:100%' : ''"
  234. ref="tableRef"
  235. style="position: relative;width: auto;"
  236. >
  237. <thead>
  238. <tr ref="rowTable">
  239. <!-- 行头 -->
  240. <th class="th-tg sm" style="width:36px" ref="cellRef"></th>
  241. <!-- 列头 -->
  242. <th
  243. :style="TableInfo.TableData.Data[0][index].ShowStyle?getShowCss(TableInfo.TableData.Data[0][index].ShowStyle,'header'):{}"
  244. v-for="(item, index) in columnHeader"
  245. :key="index"
  246. class="th-tg th-col"
  247. :data-cindex="item"
  248. :data-rindex="-1"
  249. >
  250. {{ item }}
  251. </th>
  252. </tr>
  253. </thead>
  254. <tbody>
  255. <tr ref="rowTable" v-for="(row, index) in TableInfo.TableData.Data" :key="index" :style="freezeData ? `top: ${(index- minRow+1)*tableHeight}px;` : ''" :class="freezeData && rowHeader[index] >= minRow && rowHeader[index] <= maxRow ? 'fix' : ''">
  256. <td
  257. v-for="(cell, cell_index) in row"
  258. :key="`${index}_${cell_index}`"
  259. :data-rindex="rowHeader[index]"
  260. :data-cindex="columnHeader[cell_index]"
  261. :data-datarindex="index"
  262. :data-datacindex="cell_index"
  263. :style="[cell.ShowStyle?getShowCss(cell.ShowStyle):{}, freezeData && isWithinColRange(cell_index) ? {left: (cell_index- minCol+1)*getColumnHeaderWidth(cell_index) + 'px'} : '']"
  264. :class="[freezeData && isWithinColRange(cell_index) ? 'fix-col' : '']"
  265. :initIndex="initIndex(index,cell_index,rowHeader[index],columnHeader[cell_index])"
  266. :data-key="cell.Uid"
  267. v-show="!cell.merData || cell.merData.type!=='merged'"
  268. :colspan="(cell.merData && cell.merData.type=='merge' && cell.merData.mer)?cell.merData.mer.colspan || 1:1"
  269. :rowspan="(cell.merData && cell.merData.type=='merge' && cell.merData.mer)?cell.merData.mer.rowspan || 1:1"
  270. >
  271. <!-- 插入单元格禁止编辑 -->
  272. <!-- [4,5,6,7,8].includes(cell.DataType)&&!cell.CanEdit -->
  273. <template
  274. v-if="!cell.CanEdit
  275. ||disabled
  276. ||(cell.DataType===1&&[1,2].includes(cell.DataTimeType))"
  277. >
  278. <!-- 数字格式化显示 -->
  279. <span
  280. v-if="cell.ShowStyle"
  281. :data-rindex="rowHeader[index]"
  282. :data-cindex="columnHeader[cell_index]"
  283. :data-datarindex="index"
  284. :data-datacindex="cell_index"
  285. :data-key="cell.Uid"
  286. >
  287. {{isShowDecimal(cell)?showDecimalValue(cell):isShowFormat(cell.ShowStyle)?cell.ShowFormatValue:cell.DataTime?cell.ShowValue:[8,7,6,5,1].includes(cell.DataType)?cell.ShowValue?cell.ShowValue:'-':cell.Value}}
  288. </span>
  289. <span
  290. :data-rindex="rowHeader[index]"
  291. :data-cindex="columnHeader[cell_index]"
  292. :data-datarindex="index"
  293. :data-datacindex="cell_index"
  294. :data-key="cell.Uid"
  295. v-else
  296. >{{ cell.ShowValue }}</span>
  297. </template>
  298. </td>
  299. </tr>
  300. </tbody>
  301. </table>
  302. </div>
  303. <div class="tool sheet-bottom">
  304. <div class="sheet-source"
  305. v-if="TableInfo.SourcesFrom&&JSON.parse(TableInfo.SourcesFrom).isShow"
  306. :style="`
  307. color: ${ JSON.parse(TableInfo.SourcesFrom).color };
  308. font-size: ${ JSON.parse(TableInfo.SourcesFrom).fontSize }px;
  309. `"
  310. >
  311. source:<em>{{ JSON.parse(TableInfo.SourcesFrom).text}}</em>
  312. </div>
  313. </div>
  314. </div>
  315. </template>
  316. <style lang='scss' scoped>
  317. .sheet-show-wrapper {
  318. max-width: 1200px;
  319. overflow: hidden;
  320. position: relative;
  321. margin: 0 auto;
  322. background: #fff;
  323. .tool{
  324. // text-align: right;
  325. margin-top: 5px;
  326. span{
  327. cursor: pointer;
  328. }
  329. }
  330. .sheet-bottom{
  331. display: flex;
  332. align-items: center;
  333. justify-content: space-between;
  334. white-space: nowrap;
  335. padding: 0 10px;
  336. .sheet-source{
  337. width: 30%;
  338. min-width: 150px;
  339. overflow: hidden;
  340. text-overflow: ellipsis;
  341. }
  342. .right-btns {
  343. display: flex;
  344. align-items: center;
  345. gap:15px;
  346. color: #666;
  347. }
  348. }
  349. }
  350. </style>
  351. <style scoped lang="scss">
  352. .table td,th {
  353. width: 104px;
  354. min-width: 104px;
  355. height: 35px;
  356. background: #fff;
  357. text-align: center;
  358. word-break: break-all;
  359. border: none;
  360. outline-color: #dcdfe6;
  361. outline-style: solid;
  362. outline-width: 1px;
  363. word-wrap: break-word;
  364. word-break: break-all;
  365. white-space: nowrap;
  366. overflow: hidden;
  367. text-overflow: ellipsis;
  368. position: relative;
  369. &.td-chose::after {
  370. position: absolute;
  371. top: 0;
  372. left: 0;
  373. right: 0;
  374. bottom: 0;
  375. content: "";
  376. display: block;
  377. outline: 0;
  378. border: 2px solid #0033FF;
  379. box-shadow: 0 0 5px rgba(73, 177, 249, .5)
  380. }
  381. // &.td-col-select::after {
  382. // position: absolute;
  383. // top: 0;
  384. // left: 0;
  385. // right: 0;
  386. // bottom: 0;
  387. // content: "";
  388. // display: block;
  389. // outline: 0;
  390. // border: 1px solid rgb(24, 173, 24);
  391. // border-bottom: none;
  392. // border-top: none;
  393. // }
  394. // &.td-row-select::after {
  395. // position: absolute;
  396. // top: 0;
  397. // left: 0;
  398. // right: 0;
  399. // bottom: 0;
  400. // content: "";
  401. // display: block;
  402. // outline: 0;
  403. // border: 1px solid rgb(24, 173, 24);
  404. // border-left: none;
  405. // border-right: none;
  406. // }
  407. &.insert {
  408. background: #FFEFDD;
  409. }
  410. .edbname-td {
  411. &:hover {
  412. text-decoration: underline;
  413. }
  414. }
  415. &.fix-col {
  416. position:sticky;
  417. left: 0;
  418. z-index: 98; // 表格右键操作弹窗为99
  419. }
  420. }
  421. .th-tg {
  422. background: #EBEEF5;
  423. &:hover {
  424. cursor: pointer;
  425. background: #ddd;
  426. /* border: 2px solid #409eff; */
  427. }
  428. &.sm {
  429. width: 36px;
  430. min-width: 36px;
  431. max-width: 36px;
  432. }
  433. }
  434. //整行选中
  435. tr {
  436. // position: relative;
  437. // &.choose-all::after {
  438. // position: absolute;
  439. // top: 0;
  440. // left: 0;
  441. // right: 0;
  442. // bottom: 0;
  443. // content: "";
  444. // display: block;
  445. // outline: 0;
  446. // border: 2px solid #5897fb;
  447. // box-shadow: 0 0 5px rgba(73, 177, 249, .5)
  448. // }
  449. &.fix {
  450. position:sticky;
  451. top: 0;
  452. z-index: 98; // 表格右键操作弹窗为99
  453. }
  454. }
  455. .el-icon-sort {
  456. color: #409eff;
  457. cursor: pointer;
  458. font-size: 16px;
  459. }
  460. </style>
  461. <style lang='scss' scoped>
  462. ::-webkit-scrollbar {
  463. width: 6px;
  464. height: 6px;
  465. }
  466. ::-webkit-scrollbar-track {
  467. background: rgb(239, 239, 239);
  468. border-radius: 2px;
  469. }
  470. ::-webkit-scrollbar-thumb {
  471. background: #ccc;
  472. border-radius: 10px;
  473. }
  474. ::-webkit-scrollbar-thumb:hover {
  475. background: #888;
  476. }
  477. ::-webkit-scrollbar-corner {
  478. background: #666;
  479. }
  480. .table-wrapper {
  481. max-width: calc(100vw - 20px);
  482. height: calc(100vh - 400px);
  483. margin: 0 auto;
  484. // margin-right: -5px;
  485. overflow: auto;
  486. }
  487. table {
  488. width: 100%;
  489. font-size: 14px;
  490. color: #333;
  491. td,
  492. th {
  493. // min-width: 120px;
  494. word-break: break-all;
  495. word-wrap: break-word;
  496. line-height: 1.2em;
  497. border: 1px solid #dcdfe6;
  498. // height: 40px;
  499. text-align: center;
  500. border-left: none;
  501. border-top: none;
  502. &:first-child {
  503. border-left: 1px solid #dcdfe6;
  504. }
  505. }
  506. .data-cell{
  507. color: #333;
  508. &.one-bg {
  509. background-color: #EFEEF1;
  510. }
  511. &.two-bg {
  512. background-color: #fff;
  513. }
  514. }
  515. .thead-sticky {
  516. position: sticky;
  517. top: 0;
  518. }
  519. .head-column {
  520. background-color: #505B78;
  521. color: #fff;
  522. }
  523. .split-word {
  524. span { display: inline; }
  525. }
  526. }
  527. .no-water{
  528. td,
  529. th {
  530. background-color: #fff;
  531. }
  532. .head-column {
  533. background-color: #505B78;
  534. color: #fff;
  535. }
  536. }
  537. .pc-sty table {
  538. table-layout: auto;
  539. td,th {
  540. width: auto;
  541. height: auto;
  542. padding: 0.4em 0;
  543. }
  544. }
  545. .mobile-sty table {
  546. table-layout: auto;
  547. td,th {
  548. min-width: 120px;
  549. height: 40px;
  550. }
  551. }
  552. .background-watermark{
  553. background-repeat: no-repeat;
  554. background-position: center center;
  555. background-size: 100%;
  556. }
  557. </style>