BalanceSheet.vue 16 KB

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