Table.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  1. <template>
  2. <div>
  3. <div ref="spreadsheet" class="spreadsheet-container" id="spreadsheet"></div>
  4. </div>
  5. </template>
  6. <script>
  7. import Spreadsheet from "x-data-spreadsheet"
  8. import { indexedColors } from "./color"
  9. import zhCN from "x-data-spreadsheet/dist/locale/zh-cn"
  10. import _ from "lodash-es"
  11. import tinycolor from "tinycolor2"
  12. Spreadsheet.locale("zh-cn", zhCN)
  13. export default {
  14. name: "ExcelViewer",
  15. props: {
  16. workbook: Object,
  17. },
  18. data() {
  19. return {
  20. spreadsheet: null,
  21. themeColors: [],
  22. }
  23. },
  24. mounted() {
  25. this.initSpreadsheet()
  26. },
  27. watch: {
  28. workbook() {
  29. this.parseTheme()
  30. this.updateTable()
  31. },
  32. },
  33. computed: {
  34. sheets() {
  35. if (this.workbook.worksheets) {
  36. return this.workbook.worksheets.filter((sheet) => sheet._rows.length)
  37. }
  38. return []
  39. },
  40. },
  41. methods: {
  42. initSpreadsheet() {
  43. // 优化性能配置
  44. this.spreadsheet = new Spreadsheet(this.$refs.spreadsheet, {
  45. view: {
  46. height: () => document.documentElement.clientHeight - 120,
  47. width: () => document.documentElement.clientWidth - 40,
  48. },
  49. mode: "read", // 只读模式
  50. showToolbar: false,
  51. showGrid: true,
  52. showContextmenu: false, // 禁用右键菜单提高性能
  53. multipleSheets: true,
  54. rpx: 1, // 减少渲染计算
  55. row: {
  56. len: 100, // 限制初始行数
  57. height: 25, // 固定行高
  58. autoHeight: true,
  59. },
  60. col: {
  61. len: 26, // 限制初始列数
  62. width: 50, // 固定列宽
  63. indexWidth: 60, // 行索引宽度
  64. minWidth: 30, // 最小列宽
  65. autoWidth: true, // 自动调整列宽
  66. },
  67. style: {
  68. // 确保默认样式正确
  69. bgcolor: "#ffffff",
  70. color: "#333333",
  71. align: "left",
  72. valign: "middle",
  73. textwrap: false,
  74. strike: false,
  75. underline: false,
  76. italic: false,
  77. bold: false,
  78. fontSize: 12,
  79. },
  80. }).loadData({})
  81. // 延迟加载数据,避免初始化时卡死
  82. this.$nextTick(() => {
  83. this.parseTheme()
  84. this.updateSpreadsheet()
  85. })
  86. },
  87. updateSpreadsheet() {
  88. if (!this.spreadsheet) return
  89. // 显示加载状态
  90. this.$refs.spreadsheet.classList.add("loading")
  91. try {
  92. const data = this.convertWorksheetToData()
  93. if (this.spreadsheet) {
  94. this.$refs.spreadsheet.innerHTML = ""
  95. }
  96. // 重新创建实例
  97. this.spreadsheet = new Spreadsheet(this.$refs.spreadsheet, {
  98. view: {
  99. height: () => document.documentElement.clientHeight - 120,
  100. width: () => document.documentElement.clientWidth - 40,
  101. },
  102. mode: "read",
  103. showToolbar: false,
  104. showGrid: true,
  105. showContextmenu: false,
  106. multipleSheets: true,
  107. rpx: 1,
  108. row: {
  109. len: 100,
  110. height: 25,
  111. autoHeight: true,
  112. },
  113. col: {
  114. len: 26,
  115. width: 50,
  116. indexWidth: 60,
  117. minWidth: 30,
  118. autoWidth: true,
  119. },
  120. })
  121. .loadData(data)
  122. } finally {
  123. this.$refs.spreadsheet.classList.remove("loading")
  124. }
  125. },
  126. convertWorksheetToData() {
  127. let data = []
  128. this.sheets.forEach((sheet) => {
  129. const sheetIndex = sheet.id
  130. const sheetData = {
  131. name: sheet._name,
  132. freeze: "A1",
  133. styles: [
  134. {
  135. bgcolor: "#ffffff",
  136. color: "#000000",
  137. align: "left",
  138. valign: "middle",
  139. fontSize: 12,
  140. },
  141. ],
  142. merges: [],
  143. rows: {},
  144. cols: {},
  145. }
  146. try {
  147. // 预处理合并单元格
  148. this.processMerges(sheet, sheetData)
  149. // 预处理样式缓存,避免重复创建相同样式
  150. const styleCache = new Map()
  151. // 处理单元格数据
  152. this.processRows(sheet, sheetData, styleCache)
  153. // 处理列宽
  154. this.processColumns(sheet, sheetData)
  155. data.push(sheetData)
  156. } catch (error) {
  157. data[sheetIndex] = {
  158. name: sheet._name || "Sheet1",
  159. rows: {},
  160. cols: {},
  161. }
  162. }
  163. })
  164. return data
  165. },
  166. // 处理合并单元格
  167. processMerges(sheet, sheetData) {
  168. if (!sheet._merges) return
  169. Object.values(sheet._merges).forEach((merge) => {
  170. const { top, left, bottom, right } = merge
  171. sheetData.merges.push(
  172. `${this.columnIndexToLetter(
  173. left - 1
  174. )}${top}:${this.columnIndexToLetter(right - 1)}${bottom}`
  175. )
  176. })
  177. },
  178. // 处理行和单元格数据
  179. processRows(sheet, sheetData, styleCache) {
  180. // 增加最大行数限制,确保所有行都能显示
  181. const maxRows = Math.min(sheet.rowCount || 50, 100)
  182. // 预处理所有行,确保即使空行也能被处理
  183. for (let rowIndex = 0; rowIndex < maxRows; rowIndex++) {
  184. const row = sheet.getRow(rowIndex + 1)
  185. // 初始化行数据和行高
  186. let maxHeight = 25 // 最小行高
  187. sheetData.rows[rowIndex] = {
  188. cells: {},
  189. height: maxHeight,
  190. }
  191. // 如果行不存在,创建一个空行
  192. if (!row || !row.cellCount) {
  193. continue
  194. }
  195. // 处理行中的单元格
  196. // 使用列数而不是cellCount,确保处理所有可能的单元格
  197. const maxCols = Math.min(sheet.columnCount || 26, 100)
  198. for (let colIndex = 0; colIndex < maxCols; colIndex++) {
  199. try {
  200. const cell = row.getCell(colIndex + 1)
  201. if (!cell || (cell.type === "null" && !cell.style)) {
  202. // 对于空单元格,添加一个默认样式的空单元格
  203. sheetData.rows[rowIndex].cells[colIndex] = {
  204. text: "",
  205. style: 0,
  206. }
  207. continue
  208. }
  209. // 获取单元格文本和样式
  210. const { text: cellText, height: cellHeight } =
  211. this.getCellTextAndHeight(cell)
  212. const styleIndex = this.getCellStyle(
  213. cell,
  214. styleCache,
  215. sheetData.styles
  216. )
  217. // 更新行高
  218. maxHeight = Math.max(maxHeight, cellHeight)
  219. // 检查是否是合并单元格的起始单元格
  220. const mergeInfo = Object.values(sheet._merges || {}).find(
  221. (m) => m.top === row.number && m.left === colIndex + 1
  222. )
  223. // 设置单元格数据
  224. if (mergeInfo) {
  225. sheetData.rows[rowIndex].cells[colIndex] = {
  226. text: cellText,
  227. style: styleIndex,
  228. merge: [
  229. mergeInfo.bottom - mergeInfo.top,
  230. mergeInfo.right - mergeInfo.left,
  231. ],
  232. }
  233. } else {
  234. sheetData.rows[rowIndex].cells[colIndex] = {
  235. text: cellText,
  236. style: styleIndex,
  237. }
  238. }
  239. } catch (error) {
  240. sheetData.rows[rowIndex].cells[colIndex] = {
  241. text: "",
  242. style: 0,
  243. }
  244. }
  245. }
  246. // 更新行高
  247. sheetData.rows[rowIndex].height = maxHeight
  248. }
  249. },
  250. // 获取单元格文本和计算高度
  251. getCellTextAndHeight(cell) {
  252. let cellText = ""
  253. try {
  254. // 处理不同类型的单元格内容
  255. if (!cell || cell.value === null || cell.value === undefined) {
  256. cellText = ""
  257. } else if (
  258. cell.type === "date" ||
  259. (cell.numFmt &&
  260. (cell.numFmt.includes("yy") ||
  261. cell.numFmt.includes("mm") ||
  262. cell.numFmt.includes("dd") ||
  263. cell.numFmt.includes("h") ||
  264. cell.numFmt.includes("m:s")))
  265. ) {
  266. // 处理日期类型或带有日期格式的数字
  267. try {
  268. // 检查是否为日期值
  269. let date
  270. if (cell.value instanceof Date) {
  271. date = cell.value
  272. } else if (typeof cell.value === "number") {
  273. // Excel日期是从1900年1月1日开始的天数
  274. // 需要转换为JavaScript日期
  275. const excelEpoch = new Date(1899, 11, 30) // Excel的起始日期
  276. date = new Date(
  277. excelEpoch.getTime() + cell.value * 24 * 60 * 60 * 1000
  278. )
  279. } else {
  280. date = new Date(cell.value)
  281. }
  282. if (!isNaN(date.getTime())) {
  283. // 格式化日期
  284. const year = date.getFullYear()
  285. const month = (date.getMonth() + 1).toString().padStart(2, "0")
  286. const day = date.getDate().toString().padStart(2, "0")
  287. // 检查是否需要显示时间
  288. if (
  289. cell.numFmt &&
  290. (cell.numFmt.includes("h") || cell.numFmt.includes("s"))
  291. ) {
  292. const hour = date.getHours().toString().padStart(2, "0")
  293. const minute = date.getMinutes().toString().padStart(2, "0")
  294. const second = date.getSeconds().toString().padStart(2, "0")
  295. cellText = `${year}/${month}/${day} ${hour}:${minute}:${second}`
  296. } else {
  297. cellText = `${year}/${month}/${day}`
  298. }
  299. } else {
  300. // 如果不是有效日期,使用原始文本
  301. cellText = String(cell.text || cell.value || "")
  302. }
  303. } catch (e) {
  304. cellText = String(cell.text || cell.value || "")
  305. }
  306. } else if (typeof cell.value === "object" && cell.value !== null) {
  307. if (cell.value.hyperlink) {
  308. cellText = "[链接]"
  309. } else if (cell.value.image) {
  310. cellText = "[图片]"
  311. } else if (cell.value.richText) {
  312. cellText = cell.value.richText
  313. .map((t) => String(t?.text || ""))
  314. .join("")
  315. } else {
  316. cellText = String(cell.text || "")
  317. }
  318. } else if (cell.formula) {
  319. // 检查公式结果是否可能是日期
  320. if (
  321. cell.numFmt &&
  322. (cell.numFmt.includes("yy") ||
  323. cell.numFmt.includes("mm") ||
  324. cell.numFmt.includes("dd"))
  325. ) {
  326. try {
  327. // 尝试将结果转换为日期
  328. const excelEpoch = new Date(1899, 11, 30)
  329. const date = new Date(
  330. excelEpoch.getTime() + cell.result * 24 * 60 * 60 * 1000
  331. )
  332. if (!isNaN(date.getTime())) {
  333. const year = date.getFullYear()
  334. const month = (date.getMonth() + 1).toString().padStart(2, "0")
  335. const day = date.getDate().toString().padStart(2, "0")
  336. cellText = `${year}-${month}-${day}`
  337. } else {
  338. cellText = String(cell.result || cell.value || "")
  339. }
  340. } catch (e) {
  341. cellText = String(cell.result || cell.value || "")
  342. }
  343. } else {
  344. cellText =
  345. cell.result !== undefined && cell.result !== null
  346. ? String(cell.result)
  347. : String(cell.value || "")
  348. }
  349. } else {
  350. cellText =
  351. cell.text !== undefined && cell.text !== null
  352. ? String(cell.text)
  353. : cell.value !== undefined && cell.value !== null
  354. ? String(cell.value)
  355. : ""
  356. }
  357. } catch (e) {
  358. cellText = "[格式错误]"
  359. }
  360. // 计算行高
  361. const lineBreaks = ((cellText || "").match(/\n/g) || []).length + 1
  362. const chars = [...(cellText || "")]
  363. const lines = Math.ceil(chars.length / 40)
  364. const neededHeight = Math.max(lineBreaks, lines) * 20
  365. return { text: cellText, height: neededHeight }
  366. },
  367. // 获取单元格样式
  368. getCellStyle(cell, styleCache, styles) {
  369. // 创建样式对象
  370. const cellStyle = {
  371. bgcolor: "#ffffff",
  372. color: "#000000",
  373. align: cell.alignment?.horizontal || "left",
  374. valign: cell.alignment?.vertical || "middle",
  375. fontSize: cell.font?.size || 12,
  376. textwrap: true,
  377. bold: cell.font?.bold,
  378. italic: cell.font?.italic,
  379. underline: false,
  380. strike: false,
  381. }
  382. // 处理背景色
  383. if (cell.fill && cell.fill.type === "pattern") {
  384. const fgColor = cell.fill.fgColor || {}
  385. // 处理索引颜色
  386. if (typeof fgColor.indexed === "number") {
  387. const indexedColor = indexedColors[fgColor.indexed]
  388. if (indexedColor) {
  389. cellStyle.bgcolor = `#${indexedColor}`
  390. }
  391. }
  392. // 处理主题颜色
  393. else if (typeof fgColor.theme === "number") {
  394. // Excel主题颜色索引映射
  395. const themeColorMap = {
  396. 0: 1, // 浅色1
  397. 1: 0, // 深色1
  398. 2: 3, // 浅色2
  399. 3: 2, // 深色2
  400. 4: 4, // 强调色1
  401. 5: 5, // 强调色2
  402. 6: 6, // 强调色3
  403. 7: 7, // 强调色4
  404. 8: 8, // 强调色5
  405. 9: 9, // 强调色6
  406. }
  407. const mappedIndex =
  408. themeColorMap[fgColor.theme] !== undefined
  409. ? themeColorMap[fgColor.theme]
  410. : fgColor.theme
  411. if (this.themeColors[mappedIndex]) {
  412. let color = `#${this.themeColors[mappedIndex]}`
  413. // 应用色调调整
  414. if (typeof fgColor.tint === "number" && fgColor.tint !== 0) {
  415. color = this.applyTint(color, fgColor.tint)
  416. }
  417. cellStyle.bgcolor = color
  418. }
  419. }
  420. // 处理RGB颜色
  421. else if (fgColor.rgb) {
  422. cellStyle.bgcolor = `#${fgColor.rgb.substring(
  423. fgColor.rgb.length - 6
  424. )}`
  425. }
  426. // 处理ARGB颜色
  427. else if (fgColor.argb) {
  428. const color = this.convertArgbToHex(fgColor.argb)
  429. if (color) cellStyle.bgcolor = color
  430. }
  431. }
  432. // 处理字体颜色
  433. if (cell.font && cell.font.color) {
  434. const fontColor = cell.font.color
  435. // 处理索引颜色
  436. if (typeof fontColor.indexed === "number") {
  437. const indexedColor = indexedColors[fontColor.indexed]
  438. if (indexedColor) {
  439. cellStyle.color = `#${indexedColor}`
  440. }
  441. }
  442. // 处理主题颜色
  443. else if (typeof fontColor.theme === "number") {
  444. // Excel主题颜色索引映射
  445. const themeColorMap = {
  446. 0: 1, // 浅色1
  447. 1: 0, // 深色1
  448. 2: 3, // 浅色2
  449. 3: 2, // 深色2
  450. 4: 4, // 强调色1
  451. 5: 5, // 强调色2
  452. 6: 6, // 强调色3
  453. 7: 7, // 强调色4
  454. 8: 8, // 强调色5
  455. 9: 9, // 强调色6
  456. }
  457. const mappedIndex =
  458. themeColorMap[fontColor.theme] !== undefined
  459. ? themeColorMap[fontColor.theme]
  460. : fontColor.theme
  461. if (this.themeColors[mappedIndex]) {
  462. let color = `#${this.themeColors[mappedIndex]}`
  463. // 应用色调调整
  464. if (typeof fontColor.tint === "number" && fontColor.tint !== 0) {
  465. color = this.applyTint(color, fontColor.tint)
  466. }
  467. cellStyle.color = color
  468. }
  469. }
  470. // 处理RGB颜色
  471. else if (fontColor.rgb) {
  472. cellStyle.color = `#${fontColor.rgb.substring(
  473. fontColor.rgb.length - 6
  474. )}`
  475. }
  476. // 处理ARGB颜色
  477. else if (fontColor.argb) {
  478. const color = this.convertArgbToHex(fontColor.argb)
  479. if (color) cellStyle.color = color
  480. }
  481. }
  482. // 使用样式缓存避免重复
  483. const styleKey = JSON.stringify(cellStyle)
  484. if (styleCache.has(styleKey)) {
  485. return styleCache.get(styleKey)
  486. }
  487. // 添加新样式
  488. styles.push(cellStyle)
  489. const styleIndex = styles.length - 1
  490. styleCache.set(styleKey, styleIndex)
  491. return styleIndex
  492. },
  493. applyTint(hexColor, tint) {
  494. try {
  495. // 将十六进制颜色转换为RGB
  496. const color = tinycolor(hexColor)
  497. const rgb = color.toRgb()
  498. // 根据Excel的色调算法调整颜色
  499. const adjustColor = (value, tint) => {
  500. let result
  501. if (tint < 0) {
  502. // 负色调值,使颜色变暗
  503. result = value * (1 + tint)
  504. } else {
  505. // 正色调值,使颜色变亮
  506. result = value + (255 - value) * tint
  507. }
  508. return Math.max(0, Math.min(255, Math.round(result)))
  509. }
  510. // 应用色调到每个RGB通道
  511. const r = adjustColor(rgb.r, tint)
  512. const g = adjustColor(rgb.g, tint)
  513. const b = adjustColor(rgb.b, tint)
  514. // 返回调整后的颜色
  515. return tinycolor({ r, g, b }).toHexString()
  516. } catch (e) {
  517. return hexColor
  518. }
  519. },
  520. // 处理列宽
  521. processColumns(sheet, sheetData) {
  522. const maxCols = Math.min(sheet.columnCount || 26, 50)
  523. const colWidthCache = new Map()
  524. const mergedCols = new Set()
  525. // 收集合并单元格信息
  526. if (sheet._merges) {
  527. Object.values(sheet._merges).forEach((merge) => {
  528. // 记录被合并的列
  529. for (let col = merge.left; col <= merge.right; col++) {
  530. mergedCols.add(col - 1)
  531. }
  532. })
  533. }
  534. // 先收集所有单元格的文本宽度
  535. sheet.eachRow((row) => {
  536. for (let colIndex = 0; colIndex < maxCols; colIndex++) {
  537. try {
  538. const cell = row.getCell(colIndex + 1)
  539. if (!cell) continue
  540. // 安全获取单元格文本
  541. let cellText = ""
  542. try {
  543. if (cell.text !== undefined && cell.text !== null) {
  544. cellText = String(cell.text)
  545. } else if (cell.value !== undefined && cell.value !== null) {
  546. if (typeof cell.value === "object") {
  547. cellText = cell.value?.richText
  548. ? cell.value.richText
  549. .map((t) => String(t?.text || ""))
  550. .join("")
  551. : cell.value?.hyperlink
  552. ? "[链接]"
  553. : cell.value?.image
  554. ? "[图片]"
  555. : ""
  556. } else {
  557. cellText = String(cell.value)
  558. }
  559. }
  560. } catch (e) {
  561. cellText = ""
  562. }
  563. // 计算文本宽度
  564. if (cellText) {
  565. // 检查是否是合并单元格的一部分
  566. const isMerged = Object.values(sheet._merges || {}).some(
  567. (merge) =>
  568. row.number >= merge.top &&
  569. row.number <= merge.bottom &&
  570. colIndex + 1 >= merge.left &&
  571. colIndex + 1 <= merge.right
  572. )
  573. // 如果是合并单元格,根据合并的列数调整宽度计算
  574. if (isMerged) {
  575. const merge = Object.values(sheet._merges || {}).find(
  576. (m) =>
  577. row.number >= m.top &&
  578. row.number <= m.bottom &&
  579. colIndex + 1 >= m.left &&
  580. colIndex + 1 <= m.right
  581. )
  582. if (merge && colIndex + 1 === merge.left) {
  583. // 只为合并单元格的第一列计算宽度
  584. const mergeWidth = merge.right - merge.left + 1
  585. const textWidth = [...cellText].reduce((width, char) => {
  586. return width + (/[\u4e00-\u9fa5]/.test(char) ? 2 : 1)
  587. }, 0)
  588. // 平均分配到每一列
  589. const avgWidth = Math.ceil(textWidth / mergeWidth)
  590. colWidthCache.set(
  591. colIndex,
  592. Math.max(colWidthCache.get(colIndex) || 0, avgWidth)
  593. )
  594. }
  595. } else {
  596. // 普通单元格
  597. const textWidth = [...cellText].reduce((width, char) => {
  598. return width + (/[\u4e00-\u9fa5]/.test(char) ? 2 : 1)
  599. }, 0)
  600. colWidthCache.set(
  601. colIndex,
  602. Math.max(colWidthCache.get(colIndex) || 0, textWidth)
  603. )
  604. }
  605. }
  606. } catch (e) {
  607. // 忽略错误
  608. }
  609. }
  610. })
  611. // 设置列宽
  612. for (let colIndex = 0; colIndex < maxCols; colIndex++) {
  613. let maxWidth = colWidthCache.get(colIndex) || 0
  614. // 对于合并单元格的列,限制最大宽度
  615. if (mergedCols.has(colIndex)) {
  616. maxWidth = Math.min(maxWidth, 30) // 限制合并列的宽度
  617. }
  618. // 设置合理的列宽范围
  619. const width = Math.max(40, Math.min(300, maxWidth * 9))
  620. sheetData.cols[colIndex] = {
  621. width: width,
  622. }
  623. }
  624. },
  625. // ARGB 转 Hex 颜色方法
  626. convertArgbToHex(argb) {
  627. if (!argb) return null
  628. try {
  629. const matches =
  630. /^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(argb)
  631. if (!matches) return null
  632. return `#${matches[2]}${matches[3]}${matches[4]}`
  633. } catch (e) {
  634. return null
  635. }
  636. },
  637. columnIndexToLetter(index) {
  638. let temp
  639. let letter = ""
  640. // 修复列索引转字母的算法
  641. if (index < 0) return ""
  642. do {
  643. temp = index % 26
  644. letter = String.fromCharCode(temp + 65) + letter
  645. index = Math.floor(index / 26) - 1
  646. } while (index >= 0)
  647. return letter
  648. },
  649. parseTheme() {
  650. const theme = this.workbook._themes?.theme1
  651. if (!theme) {
  652. // Office默认主题颜色
  653. this.themeColors = [
  654. "FFFFFF", // 白色 - 浅色1
  655. "000000", // 黑色 - 深色1
  656. "EEECE1", // 浅灰 - 浅色2
  657. "1F497D", // 深灰 - 深色2
  658. "4F81BD", // 蓝色 - 强调色1
  659. "C0504D", // 红色 - 强调色2
  660. "9BBB59", // 绿色 - 强调色3
  661. "8064A2", // 紫色 - 强调色4
  662. "4BACC6", // 青色 - 强调色5
  663. "F79646", // 橙色 - 强调色6
  664. ]
  665. return
  666. }
  667. try {
  668. const parser = new DOMParser()
  669. const doc = parser.parseFromString(theme, "text/xml")
  670. // 获取颜色方案元素
  671. const clrScheme = doc.getElementsByTagName("a:clrScheme")[0]
  672. if (!clrScheme) {
  673. throw new Error("找不到颜色方案元素")
  674. }
  675. // 初始化主题颜色数组
  676. this.themeColors = []
  677. const colorElements = Array.from(clrScheme.children)
  678. // 处理每个颜色元素
  679. for (const element of colorElements) {
  680. let colorValue = null
  681. // 查找颜色定义元素
  682. const srgbClr = element.getElementsByTagName("a:srgbClr")[0]
  683. const sysClr = element.getElementsByTagName("a:sysClr")[0]
  684. if (srgbClr) {
  685. colorValue = srgbClr.getAttribute("val")
  686. } else if (sysClr) {
  687. colorValue =
  688. sysClr.getAttribute("lastClr") || sysClr.getAttribute("val")
  689. }
  690. this.themeColors.push(colorValue || "FFFFFF")
  691. }
  692. } catch (error) {
  693. this.themeColors = [
  694. "FFFFFF", // 白色 - 浅色1
  695. "000000", // 黑色 - 深色1
  696. "EEECE1", // 浅灰 - 浅色2
  697. "1F497D", // 深灰 - 深色2
  698. "4F81BD", // 蓝色 - 强调色1
  699. "C0504D", // 红色 - 强调色2
  700. "9BBB59", // 绿色 - 强调色3
  701. "8064A2", // 紫色 - 强调色4
  702. "4BACC6", // 青色 - 强调色5
  703. "F79646", // 橙色 - 强调色6
  704. ]
  705. }
  706. },
  707. },
  708. }
  709. </script>
  710. <style scoped>
  711. .spreadsheet-container {
  712. width: 100%;
  713. height: calc(100vh - 120px);
  714. position: relative;
  715. }
  716. .spreadsheet-container.loading::after {
  717. content: "加载中...";
  718. position: absolute;
  719. top: 0;
  720. left: 0;
  721. right: 0;
  722. bottom: 0;
  723. background: rgba(255, 255, 255, 0.7);
  724. display: flex;
  725. align-items: center;
  726. justify-content: center;
  727. font-size: 18px;
  728. z-index: 1000;
  729. }
  730. .sheet-btn {
  731. border: 1px solid #ccc;
  732. background-color: #f0f0f0;
  733. cursor: pointer;
  734. margin-right: 5px;
  735. border-radius: 4px;
  736. padding: 5px 15px;
  737. }
  738. .sheet-btn.active {
  739. background-color: #4caf50;
  740. color: white;
  741. border-color: #388e3c;
  742. }
  743. .btn-group {
  744. margin-bottom: 10px;
  745. display: flex;
  746. flex-wrap: wrap;
  747. gap: 5px;
  748. padding: 8px;
  749. background-color: #f8f8f8;
  750. border-radius: 4px;
  751. }
  752. </style>