BalanceSheet.vue 16 KB

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