luckysheet.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="utf-8">
  5. <link rel='stylesheet' href='lib/luckysheet/plugins/css/pluginsCss.css' />
  6. <link rel='stylesheet' href='lib/luckysheet/plugins/plugins.css' />
  7. <link rel='stylesheet' href='lib/luckysheet/css/luckysheet.css' />
  8. <link rel='stylesheet' href='lib/luckysheet/assets/iconfont/iconfont.css' />
  9. <script src="lib/luckysheet/plugins/js/plugin.js"></script>
  10. <script src="lib/luckysheet/luckysheet.umd.js"></script>
  11. <style>
  12. body, html {
  13. margin: 0;
  14. padding: 0;
  15. width: 100%;
  16. height: 100%;
  17. overflow: hidden;
  18. }
  19. #luckysheet {
  20. margin: 0;
  21. padding: 0;
  22. width: 100%;
  23. height: 100%;
  24. }
  25. </style>
  26. </head>
  27. <body>
  28. <div id="luckysheet"></div>
  29. <script>
  30. let isInitialized = false
  31. // 修复luckysheet公式数据
  32. function fixSheetDataFormulas(sheetData) {
  33. if (!sheetData || !Array.isArray(sheetData)) return sheetData
  34. return sheetData.map(sheet => {
  35. if (!sheet) return sheet
  36. // 第一步:修复公式语法问题
  37. sheet = fixFormulaSyntax(sheet)
  38. // 第二步:修复计算链问题
  39. sheet = fixLuckysheetCalcChain(sheet)
  40. return sheet
  41. })
  42. }
  43. // 修复公式语法问题(与导入模块相同的逻辑)
  44. function fixFormulaSyntax(sheet) {
  45. if (!sheet) return sheet
  46. // 修复celldata中的公式
  47. if (Array.isArray(sheet.celldata)) {
  48. sheet.celldata.forEach(cell => {
  49. if (cell && cell.v && cell.v.f) {
  50. let formula = cell.v.f
  51. let newFormula = formula
  52. // 将 _xleta.N 替换为 "N"
  53. if (formula.includes('_xleta.')) {
  54. newFormula = newFormula.replace(/_xleta\.([A-Za-z]+)/g, (match, variable) => {
  55. // 如果是单个字母,替换为字符串
  56. if (/^[A-Za-z]$/.test(variable)) {
  57. return `"${variable}"`
  58. }
  59. return variable
  60. })
  61. }
  62. // 修复百分比格式问题
  63. // 将百分比转换为数值,因为Excel中100%就是数值1,0.01%就是0.0001
  64. // 支持整数和小数百分比,如 100%、0.01%、1.5%
  65. // 第一步:先修复可能存在的重复小数点问题(如0.0.01应该修正为0.0001)
  66. const duplicateDotRegex = /(\d+)\.(\d+)\.(\d+)/g
  67. const duplicateDotMatches = [...newFormula.matchAll(duplicateDotRegex)]
  68. if (duplicateDotMatches.length > 0) {
  69. duplicateDotMatches.forEach(match => {
  70. const fullMatch = match[0]
  71. const integerPart = match[1] // 如 "0"
  72. const firstDecimalPart = match[2] // 如 "0"
  73. const secondDecimalPart = match[3] // 如 "01"
  74. // 处理常见的百分比解析错误模式
  75. let correctNumber
  76. if (integerPart === '0' && firstDecimalPart === '0') {
  77. // 针对 0.0.xxx 模式的特殊修复
  78. // 规则:转换为 0.00xxx,其中 xxx 是 secondDecimalPart
  79. correctNumber = parseFloat(`0.00${secondDecimalPart}`)
  80. } else {
  81. // 其他情况:使用更保守的修复
  82. correctNumber = parseFloat(`${integerPart}.${firstDecimalPart}${secondDecimalPart}`)
  83. }
  84. newFormula = newFormula.replace(fullMatch, correctNumber.toString())
  85. })
  86. }
  87. // 第二步:处理百分比转换为数值的问题
  88. const percentRegex = /(\d+(?:\.\d+)?)%/g
  89. const percentMatches = [...newFormula.matchAll(percentRegex)]
  90. if (percentMatches.length > 0) {
  91. percentMatches.forEach(match => {
  92. const fullMatch = match[0]
  93. const number = match[1]
  94. const percentValue = parseFloat(number) / 100
  95. // 将百分比转换为数值
  96. newFormula = newFormula.replace(fullMatch, percentValue.toString())
  97. })
  98. }
  99. if (newFormula !== formula) {
  100. cell.v.f = newFormula
  101. }
  102. }
  103. })
  104. }
  105. // 修复data数组中的公式
  106. if (Array.isArray(sheet.data)) {
  107. sheet.data.forEach(row => {
  108. if (Array.isArray(row)) {
  109. row.forEach(cell => {
  110. if (cell && cell.f) {
  111. let formula = cell.f
  112. let newFormula = formula
  113. // 将 _xleta.N 替换为 "N"
  114. if (formula.includes('_xleta.')) {
  115. newFormula = newFormula.replace(/_xleta\.([A-Za-z]+)/g, (match, variable) => {
  116. // 如果是单个字母,替换为字符串
  117. if (/^[A-Za-z]$/.test(variable)) {
  118. return `"${variable}"`
  119. }
  120. return variable
  121. })
  122. }
  123. // 修复百分比格式问题
  124. // 支持整数和小数百分比,如 100%、0.01%、1.5%
  125. // 第一步:先修复可能存在的重复小数点问题(如0.0.01应该修正为0.0001)
  126. const duplicateDotRegex = /(\d+)\.(\d+)\.(\d+)/g
  127. const duplicateDotMatches = [...newFormula.matchAll(duplicateDotRegex)]
  128. if (duplicateDotMatches.length > 0) {
  129. duplicateDotMatches.forEach(match => {
  130. const fullMatch = match[0]
  131. const integerPart = match[1] // 如 "0"
  132. const firstDecimalPart = match[2] // 如 "0"
  133. const secondDecimalPart = match[3] // 如 "01"
  134. // 处理常见的百分比解析错误模式
  135. let correctNumber
  136. if (integerPart === '0' && firstDecimalPart === '0') {
  137. // 针对 0.0.xxx 模式的特殊修复
  138. // 规则:转换为 0.00xxx,其中 xxx 是 secondDecimalPart
  139. correctNumber = parseFloat(`0.00${secondDecimalPart}`)
  140. } else {
  141. // 其他情况:使用更保守的修复
  142. correctNumber = parseFloat(`${integerPart}.${firstDecimalPart}${secondDecimalPart}`)
  143. }
  144. newFormula = newFormula.replace(fullMatch, correctNumber.toString())
  145. })
  146. }
  147. // 第二步:处理百分比转换为数值的问题
  148. const percentRegex = /(\d+(?:\.\d+)?)%/g
  149. const percentMatches = [...newFormula.matchAll(percentRegex)]
  150. if (percentMatches.length > 0) {
  151. percentMatches.forEach(match => {
  152. const fullMatch = match[0]
  153. const number = match[1]
  154. const percentValue = parseFloat(number) / 100
  155. // 将百分比转换为数值
  156. newFormula = newFormula.replace(fullMatch, percentValue.toString())
  157. })
  158. }
  159. if (newFormula !== formula) {
  160. cell.f = newFormula
  161. }
  162. }
  163. })
  164. }
  165. })
  166. }
  167. return sheet
  168. }
  169. /**
  170. * 修复luckysheet计算链问题
  171. * 确保所有包含公式的单元格都能正常计算
  172. * 这是解决"G11和H11公式工作,但I11及以后不工作"问题的关键
  173. *
  174. * @param {Object} sheet - 工作表数据
  175. * @returns {Object} 修复后的工作表
  176. */
  177. function fixLuckysheetCalcChain(sheet) {
  178. if (!sheet) return sheet
  179. // 确保calcChain存在
  180. if (!sheet.calcChain) {
  181. sheet.calcChain = []
  182. }
  183. // 创建现有计算链的快速查找映射
  184. const existingCalcMap = new Map()
  185. sheet.calcChain.forEach(item => {
  186. if (item && typeof item.r === 'number' && typeof item.c === 'number') {
  187. const key = `${item.r}_${item.c}`
  188. existingCalcMap.set(key, true)
  189. }
  190. })
  191. /**
  192. * 添加单元格到计算链
  193. * @param {number} row - 行索引
  194. * @param {number} col - 列索引
  195. */
  196. const addToCalcChain = (row, col) => {
  197. const key = `${row}_${col}`
  198. // 如果已经存在,跳过
  199. if (existingCalcMap.has(key)) {
  200. return
  201. }
  202. // 添加到计算链
  203. sheet.calcChain.push({
  204. r: row,
  205. c: col,
  206. index: sheet.index || "0"
  207. })
  208. // 更新映射
  209. existingCalcMap.set(key, true)
  210. }
  211. // 遍历celldata,查找有公式的单元格
  212. if (Array.isArray(sheet.celldata)) {
  213. sheet.celldata.forEach(cell => {
  214. if (cell && typeof cell.r === 'number' && typeof cell.c === 'number') {
  215. // 检查是否有公式
  216. const hasFormula = cell.v && cell.v.f
  217. if (hasFormula) {
  218. addToCalcChain(cell.r, cell.c)
  219. }
  220. }
  221. })
  222. }
  223. // 遍历data数组,查找有公式的单元格
  224. if (Array.isArray(sheet.data)) {
  225. sheet.data.forEach((row, rowIndex) => {
  226. if (Array.isArray(row)) {
  227. row.forEach((cell, colIndex) => {
  228. if (cell && cell.f) {
  229. addToCalcChain(rowIndex, colIndex)
  230. }
  231. })
  232. }
  233. })
  234. }
  235. return sheet
  236. }
  237. window.addEventListener('message', function(event) {
  238. const { type, data, readonly } = event.data
  239. if (type === 'init' && !isInitialized) {
  240. luckysheet.create({
  241. container: 'luckysheet',
  242. lang: 'zh', // 设置中文语言,工具栏提示为中文
  243. // 控制顶部工具栏显示:
  244. // - readonly=true (查阅模式): showtoolbar=false 隐藏工具栏
  245. // - readonly=false (添加/编辑模式): showtoolbar=true 显示工具栏
  246. showtoolbar: !readonly,
  247. showinfobar: false,
  248. showsheetbar: true,
  249. // 控制是否允许增加行/列
  250. enableAddRow: !readonly,
  251. enableAddCol: !readonly,
  252. userInfo: false,
  253. // 状态栏设置(包含缩放控制):
  254. // - showstatisticBar: true 显示状态栏(包含缩放控制)
  255. showstatisticBar: true,
  256. // 控制编辑栏显示:
  257. // - readonly=true: sheetFormulaBar=false 隐藏编辑栏
  258. // - readonly=false: sheetFormulaBar=true 显示编辑栏
  259. sheetFormulaBar: !readonly,
  260. allowEdit: !readonly,
  261. // 启用缩放控制 - 优化配置
  262. showtoolbarConfig: {
  263. zoom: true, // 启用缩放控制,包括右下角的缩放组件
  264. },
  265. // 缩放比例配置
  266. zoomRatio: {
  267. default: 100, // 默认缩放比例
  268. max: 400, // 最大缩放比例
  269. min: 10 // 最小缩放比例
  270. },
  271. // 显示行号列标
  272. showRowBar: true,
  273. showColumnBar: true,
  274. // 单元格右键菜单
  275. cellRightClickConfig: {
  276. copy: true,
  277. paste: true,
  278. insertRow: !readonly,
  279. insertColumn: !readonly,
  280. deleteRow: !readonly,
  281. deleteColumn: !readonly,
  282. hideRow: !readonly,
  283. hideColumn: !readonly,
  284. rowHeight: !readonly,
  285. columnWidth: !readonly
  286. },
  287. data: data ? fixSheetDataFormulas(data) : [{ name: 'Sheet1', color: '', status: 1, order: 0, data: [[{ v: '' }]] }],
  288. hook: {}
  289. })
  290. isInitialized = true
  291. } else if (type === 'getData') {
  292. const data = luckysheet.getAllSheets()
  293. event.source.postMessage({ type: 'dataResult', data: data }, event.origin)
  294. } else if (type === 'setData') {
  295. luckysheet.destroy()
  296. isInitialized = false
  297. luckysheet.create({
  298. container: 'luckysheet',
  299. lang: 'zh',
  300. showtoolbar: !readonly,
  301. showinfobar: false,
  302. showsheetbar: true,
  303. enableAddRow: !readonly,
  304. enableAddCol: !readonly,
  305. userInfo: false,
  306. showstatisticBar: true,
  307. sheetFormulaBar: !readonly,
  308. allowEdit: !readonly,
  309. // 启用缩放控制 - 优化配置
  310. showtoolbarConfig: {
  311. zoom: true, // 启用缩放控制,包括右下角的缩放组件
  312. },
  313. // 缩放比例配置
  314. zoomRatio: {
  315. default: 100, // 默认缩放比例
  316. max: 400, // 最大缩放比例
  317. min: 10 // 最小缩放比例
  318. },
  319. // 显示行号列标
  320. showRowBar: true,
  321. showColumnBar: true,
  322. // 单元格右键菜单
  323. cellRightClickConfig: {
  324. copy: true,
  325. paste: true,
  326. insertRow: !readonly,
  327. insertColumn: !readonly,
  328. deleteRow: !readonly,
  329. deleteColumn: !readonly,
  330. hideRow: !readonly,
  331. hideColumn: !readonly,
  332. rowHeight: !readonly,
  333. columnWidth: !readonly
  334. },
  335. data: fixSheetDataFormulas(data),
  336. hook: {}
  337. })
  338. isInitialized = true
  339. }
  340. })
  341. // 通知父页面已准备好
  342. window.parent.postMessage({ type: 'ready' }, '*')
  343. </script>
  344. </body>
  345. </html>