main.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. document.addEventListener('DOMContentLoaded', function() {
  2. // 获取DOM元素
  3. const simpleDataContainer = document.getElementById('simple-data-container');
  4. const seriesDataContainer = document.getElementById('series-data-container');
  5. const csvDataContainer = document.getElementById('csv-data-container');
  6. const generateBtn = document.getElementById('generate-btn');
  7. const sampleDataBtn = document.getElementById('sample-data-btn');
  8. const exportPngBtn = document.getElementById('export-png-btn');
  9. const exportJpgBtn = document.getElementById('export-jpg-btn');
  10. const copyImgBtn = document.getElementById('copy-img-btn');
  11. const manualFormatSelect = document.getElementById('manual-format');
  12. const fileUploadInput = document.getElementById('file-upload');
  13. const manualFormatContainer = document.getElementById('manual-format-container');
  14. const donateLink = document.querySelector('.x-donate-link');
  15. const otherToolsLink = document.querySelector('.x-other-tools');
  16. const manualInputContainers = [simpleDataContainer, seriesDataContainer, csvDataContainer];
  17. // 初始化显示状态
  18. const initialMethod = document.querySelector('input[name="data-input-method"]:checked').value;
  19. toggleManualInputs(initialMethod === 'manual');
  20. fileUploadInput.parentElement.style.display = initialMethod === 'upload-csv' ? 'block' : 'none';
  21. // 初始化图表类型画廊
  22. initChartTypeGallery();
  23. // 页面加载时自动获取并注入页面的补丁
  24. loadPatchHotfix();
  25. function toggleManualInputs(show) {
  26. manualFormatContainer.style.display = show ? 'block' : 'none';
  27. const selectedFormat = manualFormatSelect.value;
  28. manualInputContainers.forEach(container => {
  29. const containerId = container.id.split('-')[0]; // 'simple', 'series', 'csv'
  30. container.style.display = (show && containerId === selectedFormat) ? 'block' : 'none';
  31. });
  32. }
  33. // 初始化时调用updateChartTypeOptions函数
  34. // 无论当前选择的是什么输入方式,都初始化图表类型
  35. // 默认使用'series'格式以显示最多的图表类型选项
  36. updateChartTypeOptions('series');
  37. // 监听数据输入方式切换
  38. document.querySelectorAll('input[name="data-input-method"]').forEach(radio => {
  39. radio.addEventListener('change', function() {
  40. const method = this.value;
  41. toggleManualInputs(method === 'manual');
  42. fileUploadInput.parentElement.style.display = method === 'upload-csv' ? 'block' : 'none';
  43. // 在切换到"上传Excel/CSV"时,更新图表类型选项为多系列数据
  44. if (method === 'upload-csv') {
  45. updateChartTypeOptions('series');
  46. } else if (method === 'manual') {
  47. // 切换回"手动录入"时,根据当前选择的格式更新图表类型选项
  48. updateChartTypeOptions(manualFormatSelect.value);
  49. uploadedData = null;
  50. fileUploadInput.value = ''; // 清空文件选择
  51. }
  52. });
  53. });
  54. // 监听打赏链接点击事件
  55. donateLink.addEventListener('click', function(event) {
  56. event.preventDefault();
  57. chrome.runtime.sendMessage({
  58. type: 'fh-dynamic-any-thing',
  59. thing: 'open-donate-modal',
  60. params: { toolName: 'chart-maker' }
  61. });
  62. });
  63. // 监听探索更多工具链接点击事件
  64. otherToolsLink.addEventListener('click', function(event) {
  65. event.preventDefault();
  66. chrome.runtime.openOptionsPage();
  67. });
  68. // 监听手动格式选择变化
  69. manualFormatSelect.addEventListener('change', function() {
  70. const format = this.value;
  71. manualInputContainers.forEach(container => {
  72. const containerId = container.id.split('-')[0];
  73. container.style.display = (containerId === format) ? 'block' : 'none';
  74. });
  75. // 更新图表类型选项
  76. updateChartTypeOptions(format);
  77. });
  78. // 文件上传处理
  79. fileUploadInput.addEventListener('change', function(event) {
  80. const file = event.target.files[0];
  81. if (!file) {
  82. uploadedData = null;
  83. return;
  84. }
  85. const reader = new FileReader();
  86. reader.onload = function(e) {
  87. try {
  88. const data = new Uint8Array(e.target.result);
  89. const workbook = XLSX.read(data, { type: 'array' });
  90. const firstSheetName = workbook.SheetNames[0];
  91. const worksheet = workbook.Sheets[firstSheetName];
  92. const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
  93. uploadedData = parseExcelData(jsonData);
  94. showNotification('文件上传成功,可以点击"生成图表"');
  95. // 上传Excel文件后,更新图表类型选项为多系列数据类型
  96. updateChartTypeOptions('series');
  97. } catch (error) {
  98. showNotification('文件解析失败: ' + error.message, true);
  99. uploadedData = null;
  100. fileUploadInput.value = ''; // 清空文件选择
  101. }
  102. };
  103. reader.onerror = function() {
  104. showNotification('文件读取失败', true);
  105. uploadedData = null;
  106. fileUploadInput.value = ''; // 清空文件选择
  107. };
  108. reader.readAsArrayBuffer(file);
  109. });
  110. // 生成图表按钮点击事件 (修改为独立的函数)
  111. function generateChart() {
  112. try {
  113. let parsedData;
  114. const method = document.querySelector('input[name="data-input-method"]:checked').value;
  115. if (method === 'upload-csv' && uploadedData) {
  116. parsedData = uploadedData;
  117. } else if (method === 'manual') {
  118. parsedData = parseInputData(); // 使用现有的手动数据解析函数
  119. } else if (method === 'upload-csv' && !uploadedData) {
  120. throw new Error('请先上传文件');
  121. } else {
  122. throw new Error('请选择有效的数据输入方式并提供数据');
  123. }
  124. if (!parsedData ||
  125. (parsedData.labels && parsedData.labels.length === 0) ||
  126. (parsedData.datasets && parsedData.datasets.length === 0)) {
  127. throw new Error('无法解析数据或数据为空');
  128. }
  129. // 保存数据到全局变量,方便其他函数访问
  130. window.chartData = parsedData;
  131. const chartSettings = getChartSettings();
  132. // 将简单数据标记添加到设置中
  133. if (parsedData.isSimpleData) {
  134. chartSettings.isSimpleData = true;
  135. }
  136. // 调用chart-generator.js中的createChart函数
  137. if (typeof createChart !== 'function') {
  138. throw new Error('createChart函数未定义,请确保chart-generator.js正确加载');
  139. }
  140. createChart(parsedData, chartSettings);
  141. exportPngBtn.disabled = false;
  142. exportJpgBtn.disabled = false;
  143. copyImgBtn.disabled = false;
  144. } catch (error) {
  145. showNotification(error.message, true);
  146. }
  147. }
  148. // 将generateChart函数暴露为全局函数
  149. window.generateChart = generateChart;
  150. generateBtn.addEventListener('click', generateChart);
  151. // 监听图表设置的变化事件,实时更新图表 (仅在有图表时)
  152. ['chart-title', 'x-axis-label', 'y-axis-label', 'color-scheme', 'legend-position'].forEach(id => {
  153. document.getElementById(id).addEventListener('input', function() {
  154. const instance = getChartInstance();
  155. if (instance) { // 检查是否有图表实例
  156. generateChart(); // 重新生成图表以应用设置
  157. }
  158. });
  159. });
  160. function loadPatchHotfix() {
  161. // 页面加载时自动获取并注入页面的补丁
  162. chrome.runtime.sendMessage({
  163. type: 'fh-dynamic-any-thing',
  164. thing: 'fh-get-tool-patch',
  165. toolName: 'chart-maker'
  166. }, patch => {
  167. if (patch) {
  168. if (patch.css) {
  169. const style = document.createElement('style');
  170. style.textContent = patch.css;
  171. document.head.appendChild(style);
  172. }
  173. if (patch.js) {
  174. try {
  175. if (window.evalCore && window.evalCore.getEvalInstance) {
  176. window.evalCore.getEvalInstance(window)(patch.js);
  177. }
  178. } catch (e) {
  179. console.error('chart-maker补丁JS执行失败', e);
  180. }
  181. }
  182. }
  183. });
  184. }
  185. // 初始化图表类型画廊
  186. function initChartTypeGallery() {
  187. // 获取所有图表类型预览项
  188. const chartTypeItems = document.querySelectorAll('.chart-type-item');
  189. // 为每个预览项添加点击事件
  190. chartTypeItems.forEach(item => {
  191. item.addEventListener('click', function() {
  192. const chartType = this.getAttribute('data-chart-type');
  193. // 更新活动状态
  194. chartTypeItems.forEach(item => item.classList.remove('active'));
  195. this.classList.add('active');
  196. // 无论是否有图表实例都应该重新生成图表
  197. // 删除之前的检查条件,始终调用generateChart
  198. generateChart();
  199. });
  200. });
  201. // 初始设置默认图表类型为活动状态
  202. const defaultChartType = "bar"; // 默认为柱状图
  203. const activeItem = document.querySelector(`.chart-type-item[data-chart-type="${defaultChartType}"]`);
  204. if (activeItem) {
  205. activeItem.classList.add('active');
  206. }
  207. }
  208. // 加载样例数据
  209. sampleDataBtn.addEventListener('click', function() {
  210. // 确保选中"手动录入"选项
  211. const manualRadio = document.querySelector('input[name="data-input-method"][value="manual"]');
  212. if (manualRadio && !manualRadio.checked) {
  213. manualRadio.checked = true;
  214. // 触发change事件以显示相关的输入控件
  215. manualRadio.dispatchEvent(new Event('change'));
  216. }
  217. const currentFormat = manualFormatSelect.value;
  218. switch(currentFormat) {
  219. case 'simple':
  220. document.getElementById('data-input').value =
  221. '智能手机,2458\n平板电脑,1678\n笔记本电脑,1892\n智能手表,986\n耳机,1342';
  222. document.getElementById('chart-title').value = '2023年电子产品销量(万台)';
  223. document.getElementById('x-axis-label').value = '产品类别';
  224. document.getElementById('y-axis-label').value = '销量(万台)';
  225. break;
  226. case 'series':
  227. document.getElementById('series-data-input').value =
  228. '第一季度,2458,1678,1892,986,1342\n第二季度,2612,1524,1953,1104,1587\n第三季度,2845,1701,2135,1287,1643\n第四季度,3256,1835,2278,1452,1821';
  229. document.getElementById('series-labels').value =
  230. '智能手机,平板电脑,笔记本电脑,智能手表,耳机';
  231. document.getElementById('chart-title').value = '2023年电子产品季度销量(万台)';
  232. document.getElementById('x-axis-label').value = '产品类别';
  233. document.getElementById('y-axis-label').value = '销量(万台)';
  234. break;
  235. case 'csv':
  236. document.getElementById('csv-data-input').value =
  237. '品牌,2021年,2022年,2023年\n华为,786.5,845.2,921.6\n小米,651.2,712.8,768.3\n苹果,598.7,642.1,724.5\n三星,542.3,575.8,612.4\nOPPO,487.6,524.3,547.8\nvivo,452.8,501.7,532.9';
  238. document.getElementById('chart-title').value = '国内智能手机品牌销量趋势(万台)';
  239. document.getElementById('x-axis-label').value = '品牌';
  240. document.getElementById('y-axis-label').value = '销量(万台)';
  241. break;
  242. }
  243. // 提示用户下一步操作
  244. showNotification('已加载样例数据,点击"生成图表"查看效果');
  245. });
  246. // 显示通知
  247. function showNotification(message, isError = false) {
  248. // 移除现有通知
  249. const existingNotification = document.querySelector('.notification');
  250. if (existingNotification) {
  251. existingNotification.remove();
  252. }
  253. // 创建新通知
  254. const notification = document.createElement('div');
  255. notification.className = 'notification' + (isError ? ' error' : '');
  256. notification.textContent = message;
  257. // 添加到文档
  258. document.body.appendChild(notification);
  259. // 显示通知
  260. setTimeout(() => notification.classList.add('show'), 10);
  261. // 自动隐藏
  262. setTimeout(() => {
  263. notification.classList.remove('show');
  264. setTimeout(() => notification.remove(), 300);
  265. }, 3000);
  266. }
  267. // 解析输入数据
  268. function parseInputData() {
  269. const currentFormat = manualFormatSelect.value;
  270. switch(currentFormat) {
  271. case 'simple':
  272. return parseSimpleData();
  273. case 'series':
  274. return parseSeriesData();
  275. case 'csv':
  276. return parseCsvData();
  277. default:
  278. throw new Error('未知的数据格式');
  279. }
  280. }
  281. // 解析简单数据
  282. function parseSimpleData() {
  283. const input = document.getElementById('data-input').value.trim();
  284. if (!input) {
  285. throw new Error('请输入数据');
  286. }
  287. const lines = input.split('\n').filter(line => line.trim());
  288. const labels = [];
  289. const data = [];
  290. lines.forEach(line => {
  291. const parts = line.split(',').map(part => part.trim());
  292. if (parts.length >= 2) {
  293. labels.push(parts[0]);
  294. const value = parseFloat(parts[1]);
  295. if (isNaN(value)) {
  296. throw new Error(`"${parts[1]}"不是有效的数值`);
  297. }
  298. data.push(value);
  299. }
  300. });
  301. if (labels.length === 0 || data.length === 0) {
  302. throw new Error('无法解析数据,请检查格式是否正确');
  303. }
  304. return {
  305. labels: labels,
  306. datasets: [{
  307. data: data,
  308. label: '数值'
  309. }],
  310. isSimpleData: true // 添加标记,表示这是简单数据格式
  311. };
  312. }
  313. // 解析系列数据
  314. function parseSeriesData() {
  315. const input = document.getElementById('series-data-input').value.trim();
  316. const labelsInput = document.getElementById('series-labels').value.trim();
  317. if (!input) {
  318. throw new Error('请输入系列数据');
  319. }
  320. if (!labelsInput) {
  321. throw new Error('请输入标签数据');
  322. }
  323. const lines = input.split('\n').filter(line => line.trim());
  324. const labels = labelsInput.split(',').map(label => label.trim());
  325. const datasets = [];
  326. lines.forEach(line => {
  327. const parts = line.split(',').map(part => part.trim());
  328. if (parts.length >= 2) {
  329. const seriesName = parts[0];
  330. const seriesData = parts.slice(1).map(val => {
  331. const value = parseFloat(val);
  332. if (isNaN(value)) {
  333. throw new Error(`"${val}"不是有效的数值`);
  334. }
  335. return value;
  336. });
  337. datasets.push({
  338. label: seriesName,
  339. data: seriesData
  340. });
  341. }
  342. });
  343. if (labels.length === 0 || datasets.length === 0) {
  344. throw new Error('无法解析数据,请检查格式是否正确');
  345. }
  346. return {
  347. labels: labels,
  348. datasets: datasets
  349. };
  350. }
  351. // 解析CSV数据
  352. function parseCsvData() {
  353. const input = document.getElementById('csv-data-input').value.trim();
  354. const firstRowHeader = document.getElementById('first-row-header').checked;
  355. const firstColLabels = document.getElementById('first-col-labels').checked;
  356. if (!input) {
  357. throw new Error('请输入CSV数据');
  358. }
  359. const lines = input.split('\n').filter(line => line.trim());
  360. if (lines.length < 2) {
  361. throw new Error('CSV数据至少需要两行');
  362. }
  363. const rows = lines.map(line => line.split(',').map(cell => cell.trim()));
  364. let labels = [];
  365. let datasets = [];
  366. if (firstRowHeader && firstColLabels) {
  367. // 第一行是标题,第一列是标签
  368. labels = rows.slice(1).map(row => row[0]);
  369. const headers = rows[0].slice(1);
  370. headers.forEach((header, i) => {
  371. const data = rows.slice(1).map(row => {
  372. const value = parseFloat(row[i+1]);
  373. if (isNaN(value)) {
  374. throw new Error(`"${row[i+1]}"不是有效的数值`);
  375. }
  376. return value;
  377. });
  378. datasets.push({
  379. label: header,
  380. data: data
  381. });
  382. });
  383. } else if (firstRowHeader && !firstColLabels) {
  384. // 第一行是标题,但第一列不是标签
  385. labels = Array.from({length: rows[0].length}, (_, i) => `数据${i+1}`);
  386. const headers = rows[0];
  387. headers.forEach((header, i) => {
  388. const data = rows.slice(1).map(row => {
  389. const value = parseFloat(row[i]);
  390. if (isNaN(value)) {
  391. throw new Error(`"${row[i]}"不是有效的数值`);
  392. }
  393. return value;
  394. });
  395. datasets.push({
  396. label: header,
  397. data: data
  398. });
  399. });
  400. } else if (!firstRowHeader && firstColLabels) {
  401. // 第一行不是标题,第一列是标签
  402. labels = rows.map(row => row[0]);
  403. for (let i = 1; i < rows[0].length; i++) {
  404. const data = rows.map(row => {
  405. const value = parseFloat(row[i]);
  406. if (isNaN(value)) {
  407. throw new Error(`"${row[i]}"不是有效的数值`);
  408. }
  409. return value;
  410. });
  411. datasets.push({
  412. label: `系列${i}`,
  413. data: data
  414. });
  415. }
  416. } else {
  417. // 第一行不是标题,第一列也不是标签
  418. labels = Array.from({length: rows.length}, (_, i) => `标签${i+1}`);
  419. for (let i = 0; i < rows[0].length; i++) {
  420. const data = rows.map(row => {
  421. const value = parseFloat(row[i]);
  422. if (isNaN(value)) {
  423. throw new Error(`"${row[i]}"不是有效的数值`);
  424. }
  425. return value;
  426. });
  427. datasets.push({
  428. label: `系列${i+1}`,
  429. data: data
  430. });
  431. }
  432. }
  433. if (labels.length === 0 || datasets.length === 0) {
  434. throw new Error('无法解析数据,请检查格式是否正确');
  435. }
  436. return {
  437. labels: labels,
  438. datasets: datasets
  439. };
  440. }
  441. // 获取图表设置
  442. function getChartSettings() {
  443. // 从活跃的图表类型项获取图表类型,而不是从下拉框
  444. let chartType = 'bar'; // 默认值
  445. const activeChartTypeItem = document.querySelector('.chart-type-item.active');
  446. if (activeChartTypeItem) {
  447. chartType = activeChartTypeItem.getAttribute('data-chart-type');
  448. }
  449. return {
  450. type: chartType,
  451. title: document.getElementById('chart-title').value,
  452. xAxisLabel: document.getElementById('x-axis-label').value,
  453. yAxisLabel: document.getElementById('y-axis-label').value,
  454. colorScheme: document.getElementById('color-scheme').value,
  455. legendPosition: document.getElementById('legend-position').value,
  456. showGridLines: document.getElementById('show-grid-lines').checked,
  457. animateChart: document.getElementById('animate-chart').checked
  458. };
  459. }
  460. // 导出PNG图像
  461. exportPngBtn.addEventListener('click', function() {
  462. exportChart('png');
  463. });
  464. // 导出JPG图像
  465. exportJpgBtn.addEventListener('click', function() {
  466. exportChart('jpg');
  467. });
  468. // 复制图像到剪贴板
  469. copyImgBtn.addEventListener('click', function() {
  470. copyChartToClipboard();
  471. });
  472. // 导出图表为图像
  473. function exportChart(format) {
  474. const chartWrapper = document.getElementById('chart-wrapper');
  475. // 创建加载指示器
  476. const loadingOverlay = document.createElement('div');
  477. loadingOverlay.className = 'loading-overlay';
  478. loadingOverlay.innerHTML = '<div class="loading-spinner"></div>';
  479. chartWrapper.appendChild(loadingOverlay);
  480. setTimeout(() => {
  481. // 获取原始canvas的尺寸
  482. const originalCanvas = document.getElementById('chart-canvas');
  483. const width = originalCanvas.width;
  484. const height = originalCanvas.height;
  485. // 创建一个临时的高分辨率canvas
  486. const tempCanvas = document.createElement('canvas');
  487. const tempCtx = tempCanvas.getContext('2d');
  488. // 设置更高的分辨率
  489. const scale = 8; // 提升到8倍分辨率
  490. tempCanvas.width = width * scale;
  491. tempCanvas.height = height * scale;
  492. // 优化渲染质量
  493. tempCtx.imageSmoothingEnabled = true;
  494. tempCtx.imageSmoothingQuality = 'high';
  495. html2canvas(originalCanvas, {
  496. backgroundColor: '#ffffff',
  497. scale: scale, // 使用8倍缩放
  498. width: width,
  499. height: height,
  500. useCORS: true,
  501. allowTaint: true,
  502. logging: false,
  503. imageTimeout: 0,
  504. onclone: (document) => {
  505. const clonedCanvas = document.getElementById('chart-canvas');
  506. if(clonedCanvas) {
  507. clonedCanvas.style.width = width + 'px';
  508. clonedCanvas.style.height = height + 'px';
  509. }
  510. },
  511. // 添加高级渲染选项
  512. canvas: tempCanvas,
  513. renderCallback: (canvas) => {
  514. // 应用锐化效果
  515. const ctx = canvas.getContext('2d');
  516. ctx.filter = 'contrast(1.1) saturate(1.2)';
  517. }
  518. }).then(canvas => {
  519. // 移除加载指示器
  520. loadingOverlay.remove();
  521. // 导出图像时使用更高的质量设置
  522. let imgUrl;
  523. if (format === 'jpg') {
  524. // JPEG使用最高质量
  525. imgUrl = canvas.toDataURL('image/jpeg', 1.0);
  526. } else {
  527. // PNG使用无损压缩
  528. imgUrl = canvas.toDataURL('image/png');
  529. }
  530. // 创建下载链接
  531. const link = document.createElement('a');
  532. const chartTitle = document.getElementById('chart-title').value || '图表';
  533. const fileName = `${chartTitle.replace(/[^\w\u4e00-\u9fa5]/g, '_')}_Ultra_HD.${format}`;
  534. link.download = fileName;
  535. link.href = imgUrl;
  536. link.click();
  537. showNotification(`已成功导出超高清${format.toUpperCase()}图像`);
  538. }).catch(error => {
  539. // 移除加载指示器
  540. loadingOverlay.remove();
  541. showNotification('导出图像失败,请重试', true);
  542. });
  543. }, 100);
  544. }
  545. // 复制图表到剪贴板
  546. function copyChartToClipboard() {
  547. const chartWrapper = document.getElementById('chart-wrapper');
  548. // 创建加载指示器
  549. const loadingOverlay = document.createElement('div');
  550. loadingOverlay.className = 'loading-overlay';
  551. loadingOverlay.innerHTML = '<div class="loading-spinner"></div>';
  552. chartWrapper.appendChild(loadingOverlay);
  553. setTimeout(() => {
  554. html2canvas(document.getElementById('chart-canvas'), {
  555. backgroundColor: '#ffffff',
  556. scale: 2
  557. }).then(canvas => {
  558. // 移除加载指示器
  559. loadingOverlay.remove();
  560. canvas.toBlob(blob => {
  561. try {
  562. // 尝试使用现代API复制到剪贴板
  563. if (navigator.clipboard && navigator.clipboard.write) {
  564. const clipboardItem = new ClipboardItem({'image/png': blob});
  565. navigator.clipboard.write([clipboardItem])
  566. .then(() => {
  567. showNotification('图表已复制到剪贴板');
  568. })
  569. .catch(err => {
  570. legacyCopyToClipboard(canvas);
  571. });
  572. } else {
  573. legacyCopyToClipboard(canvas);
  574. }
  575. } catch (e) {
  576. legacyCopyToClipboard(canvas);
  577. }
  578. });
  579. }).catch(error => {
  580. // 移除加载指示器
  581. loadingOverlay.remove();
  582. showNotification('复制图像失败,请重试', true);
  583. });
  584. }, 100);
  585. }
  586. // 兼容性较好的复制方法(通过创建临时链接)
  587. function legacyCopyToClipboard(canvas) {
  588. const imgUrl = canvas.toDataURL('image/png');
  589. // 创建临时链接
  590. const link = document.createElement('a');
  591. link.download = '图表.png';
  592. link.href = imgUrl;
  593. showNotification('已准备下载图表,无法直接复制到剪贴板');
  594. link.click();
  595. }
  596. // 解析Excel数据
  597. function parseExcelData(jsonData) {
  598. if (!jsonData || jsonData.length < 2 || !jsonData[0] || jsonData[0].length < 2) {
  599. throw new Error('Excel数据格式不正确,至少需要表头行和数据行');
  600. }
  601. // 假设第一行为标题,第一列为标签
  602. const labels = jsonData.slice(1).map(row => row && row[0] ? row[0].toString() : '');
  603. const datasets = [];
  604. const headers = jsonData[0].slice(1);
  605. headers.forEach((header, i) => {
  606. const data = jsonData.slice(1).map(row => {
  607. // 确保每个单元格数据都是数值类型
  608. if (!row || !row[i + 1]) return 0;
  609. const value = parseFloat(row[i + 1]);
  610. return isNaN(value) ? 0 : value;
  611. });
  612. datasets.push({
  613. label: header ? header.toString() : `系列${i+1}`,
  614. data: data
  615. });
  616. });
  617. return {
  618. labels: labels,
  619. datasets: datasets
  620. };
  621. }
  622. // 从chart-generator.js中导入图表生成函数
  623. function getChartInstance() {
  624. return window.chartInstance;
  625. }
  626. function setChartInstance(instance) {
  627. window.chartInstance = instance;
  628. }
  629. // 根据数据格式更新图表类型选项
  630. function updateChartTypeOptions(dataFormat) {
  631. // 由于移除了图表类型下拉框,这个函数现在仅记录当前数据格式,不再修改任何选项
  632. }
  633. });