Better Figma Layer Exporter

更方便的 Figma 图层导出,主要功能:1. 选定图层直接导出为 png 并按 dpi 分配到对应 dpi 的 drawable 文件夹; 2. 支持将 PNG 转换成 WebP 再导出; 3. 支持导出经 SVGO 优化的 svg 图片,后两者需要 https://github.com/XuQK/Android-Tool-Server 支持

当前为 2023-04-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Better Figma Layer Exporter
  3. // @name:zh-CN Better Figma Layer Exporter
  4. // @namespace https://github.com/XuQK/Better-Figma-Layer-Exporter
  5. // @version 1.0.1
  6. // @license MIT
  7. // @description A more convenient Figma layer export solution, featuring the following main functions: 1. Direct export of selected layers as PNGs and automatically assigning them to their corresponding DPI drawable folders; 2. Support for converting PNGs to WebP format before exporting; 3. Support for exporting SVGs optimized through SVGO. The latter two features require support from https://github.com/XuQK/Android-Tool-Server.
  8. // @description:zh-CN 更方便的 Figma 图层导出,主要功能:1. 选定图层直接导出为 png 并按 dpi 分配到对应 dpi 的 drawable 文件夹; 2. 支持将 PNG 转换成 WebP 再导出; 3. 支持导出经 SVGO 优化的 svg 图片,后两者需要 https://github.com/XuQK/Android-Tool-Server 支持
  9. // @author XuQK
  10. // @match https://www.figma.com/*
  11. // @icon https://github.com/XuQK/Better-Figma-Layer-Exporter/blob/master/assets/icon.jpeg?raw=true
  12. // @grant unsafeWindow
  13. // @grant GM_setValue
  14. // @grant GM_getValue
  15. // @grant GM_registerMenuCommand
  16. // @require https://cdn.jsdelivr.net/npm/sweetalert2@11
  17. // @connect *
  18. // @run-at document-end
  19. // ==/UserScript==
  20.  
  21. (function () {
  22. "use strict";
  23.  
  24. const coloredToastStyle = document.createElement("style");
  25. coloredToastStyle.innerHTML = `
  26. .colored-toast.swal2-icon-success {
  27. background-color: #a5dc86 !important;
  28. }
  29.  
  30. .colored-toast.swal2-icon-error {
  31. background-color: #f27474 !important;
  32. }
  33.  
  34. .colored-toast.swal2-icon-warning {
  35. background-color: #f8bb86 !important;
  36. }
  37.  
  38. .colored-toast.swal2-icon-info {
  39. background-color: #3fc3ee !important;
  40. }
  41.  
  42. .colored-toast.swal2-icon-question {
  43. background-color: #87adbd !important;
  44. }
  45.  
  46. .colored-toast .swal2-title {
  47. color: white;
  48. }
  49.  
  50. .colored-toast .swal2-close {
  51. color: white;
  52. }
  53.  
  54. .colored-toast .swal2-html-container {
  55. color: white;
  56. }
  57. `;
  58. document.head.appendChild(coloredToastStyle);
  59.  
  60. GM_registerMenuCommand("Settings/设置", showSettingsDialog, "S");
  61.  
  62. function showSettingsDialog() {
  63. Toast.fire({
  64. title: "Settings / 设置",
  65. html: `
  66. <div style="display: flex; align-items: center">
  67. <label for="kd-figma-token" style="font-size: 18px; width: 8em">Figma token</label>
  68. <input id="kd-figma-token" class="swal2-input" style="margin: 8px" value="${figmaToken}">
  69. </div>
  70. <div style="display: flex; align-items: center">
  71. <label for="kd-server-host" style="font-size: 18px; width: 8em">Server host</label>
  72. <input id="kd-server-host" class="swal2-input" style="margin: 8px" value="${serverUrl}">
  73. </div>
  74. <div style="display: flex; align-items: center">
  75. <label for="kd-svg-precision" style="font-size: 18px; width: 8em">Svg precision</label>
  76. <input id="kd-svg-precision" class="swal2-input" style="margin: 8px" value="${svgPrecision}">
  77. </div>
  78. <div style="display: flex; align-items: center">
  79. <label for="kd-webp-quality" style="font-size: 18px; width: 8em">WebP quality</label>
  80. <input id="kd-webp-quality" class="swal2-input" style="margin: 8px" value="${convertWebpQuality}">
  81. </div>
  82. `,
  83. width: 500,
  84. focusConfirm: false,
  85. showCancelButton: true,
  86. preConfirm: () => {
  87. return [
  88. document.getElementById("kd-figma-token").value,
  89. document.getElementById("kd-server-host").value,
  90. document.getElementById("kd-svg-precision").value,
  91. document.getElementById("kd-webp-quality").value
  92. ];
  93. }
  94. }).then(value => {
  95. const params = value.value;
  96. figmaToken = params[0];
  97. serverUrl = params[1];
  98. svgPrecision = params[2];
  99. convertWebpQuality = params[3];
  100.  
  101. GM_setValue("figmaToken", figmaToken);
  102. GM_setValue("serverUrl", serverUrl);
  103. GM_setValue("svgPrecision", svgPrecision);
  104. GM_setValue("webpQuality", convertWebpQuality);
  105. });
  106. }
  107.  
  108. // 默认配置
  109. let figmaToken = GM_getValue("figmaToken", "");
  110. let serverUrl = GM_getValue("serverUrl", "");
  111. // svg 专用
  112. let svgPrecision = GM_getValue("svgPrecision", 1);
  113. // png 专用
  114. // 自动转换为 webp
  115. // const convertToWebp = true;
  116. // webp 转换精度,0-100
  117. let convertWebpQuality = GM_getValue("webpQuality", 75);
  118.  
  119. class Image {
  120. /**
  121. * @type {string}
  122. */
  123. url;
  124.  
  125. /**
  126. * @type {Blob} 从 figma 下载的原始图层内容,可能是 svg,也有可能是 png
  127. */
  128. originalContent;
  129.  
  130. /**
  131. * @type {number}
  132. */
  133. scale;
  134.  
  135. /**
  136. * @type {Blob} 经处理后的数据,可能是优化后的 svg,也有可能是经 png 转换过后的 webp
  137. */
  138. processedContent;
  139.  
  140. /**
  141. * @type {string} 最终创建文件的格式/后缀名
  142. */
  143. format;
  144.  
  145. /**
  146. * @type {Blob} 最终存储到文件的数据
  147. */
  148. finalContent;
  149.  
  150. /**
  151. * @param id {string}
  152. * @param name {string}
  153. */
  154. constructor(id, name) {
  155. this.id = id;
  156. this.name = name;
  157. }
  158. }
  159.  
  160. const svgOptimizerUrl = `${serverUrl}/svg-optimizer`;
  161. const pngConvertToWebpUrl = `${serverUrl}/png-convert-webp`;
  162.  
  163. const dirNameToScaleMap = new Map();
  164. dirNameToScaleMap.set("drawable-ldpi", 0.75);
  165. dirNameToScaleMap.set("drawable-mdpi", 1);
  166. dirNameToScaleMap.set("drawable-hdpi", 1.5);
  167. dirNameToScaleMap.set("drawable-xhdpi", 2);
  168. dirNameToScaleMap.set("drawable-xxhdpi", 3);
  169. dirNameToScaleMap.set("drawable-xxxhdpi", 4);
  170.  
  171. const scaleToDirNameMap = new Map();
  172. scaleToDirNameMap.set(0.75, "drawable-ldpi");
  173. scaleToDirNameMap.set(1, "drawable-mdpi");
  174. scaleToDirNameMap.set(1.5, "drawable-hdpi");
  175. scaleToDirNameMap.set(2, "drawable-xhdpi");
  176. scaleToDirNameMap.set(3, "drawable-xxhdpi");
  177. scaleToDirNameMap.set(4, "drawable-xxxhdpi");
  178.  
  179. const svgButtonId = "svgo-button";
  180. const svgoButton = document.createElement("button");
  181. svgoButton.id = svgButtonId;
  182. svgoButton.className = "basic_form--btn--t7Y67 ellipsis--ellipsis--70pHK text--fontPos11--rO47d text--_fontBase--VaHfk button_row--btnNarrow--bKZDj button_row--btn--0W3Mm basic_form--btn--t7Y67 ellipsis--ellipsis--70pHK text--fontPos11--rO47d text--_fontBase--VaHfk";
  183. svgoButton.style.marginTop = "16px";
  184. svgoButton.style.width = "90%";
  185. svgoButton.style.marginLeft = "auto";
  186. svgoButton.style.marginRight = "auto";
  187. svgoButton.innerText = "经 SVGO 优化并导出";
  188. svgoButton.addEventListener("click", function () {
  189. onClickDownloadSvg().then();
  190. });
  191.  
  192. const pngButtonId = "png-button";
  193. const pngButton = document.createElement("button");
  194. pngButton.id = pngButtonId;
  195. pngButton.className = "basic_form--btn--t7Y67 ellipsis--ellipsis--70pHK text--fontPos11--rO47d text--_fontBase--VaHfk button_row--btnNarrow--bKZDj button_row--btn--0W3Mm basic_form--btn--t7Y67 ellipsis--ellipsis--70pHK text--fontPos11--rO47d text--_fontBase--VaHfk";
  196. pngButton.style.marginTop = "16px";
  197. pngButton.style.width = "90%";
  198. pngButton.style.marginLeft = "auto";
  199. pngButton.style.marginRight = "auto";
  200. pngButton.innerText = "导出 PNG 到指定 res 目录";
  201. pngButton.addEventListener("click", function () {
  202. onClickDownloadPng(false).then();
  203. });
  204.  
  205. const webpButtonId = "webp-button";
  206. const webpButton = document.createElement("button");
  207. webpButton.id = webpButtonId;
  208. webpButton.className = "basic_form--btn--t7Y67 ellipsis--ellipsis--70pHK text--fontPos11--rO47d text--_fontBase--VaHfk button_row--btnNarrow--bKZDj button_row--btn--0W3Mm basic_form--btn--t7Y67 ellipsis--ellipsis--70pHK text--fontPos11--rO47d text--_fontBase--VaHfk";
  209. webpButton.style.width = "90%";
  210. webpButton.style.marginTop = "16px";
  211. webpButton.style.marginLeft = "auto";
  212. webpButton.style.marginRight = "auto";
  213. webpButton.innerText = "导出 WebP 到指定 res 目录";
  214. webpButton.addEventListener("click", function () {
  215. onClickDownloadPng(true).then();
  216. });
  217.  
  218. // 监听 body 元素变动,根据情况插入导出按钮
  219. new MutationObserver(() => {
  220. const container = document.querySelector("div.raw_components--panel--YDedw.export_panel--standalonePanel--yXYPM");
  221. if (container !== null) {
  222. if (document.getElementById(svgButtonId) === null) {
  223. container.parentElement.appendChild(svgoButton);
  224. }
  225. if (document.getElementById(pngButtonId) === null) {
  226. container.parentElement.appendChild(pngButton);
  227. }
  228. if (document.getElementById(webpButtonId) === null) {
  229. container.parentElement.appendChild(webpButton);
  230. }
  231. }
  232. }).observe(document.body, {childList: true, subtree: true});
  233.  
  234. const Toast = Swal.mixin({
  235. position: "center",
  236. allowOutsideClick: false
  237. });
  238.  
  239. // SVGO 优化下载功能 START
  240. async function onClickDownloadSvg() {
  241. const layerList = getSelectedLayerList();
  242. if (layerList.length === 0) {
  243. showError("未选择图层");
  244. return;
  245. }
  246. const fileKey = figma.fileKey;
  247. const dirHandle = await unsafeWindow.showDirectoryPicker({id: `${fileKey}-svg`, mode: "readwrite"});
  248. showExporting();
  249. try {
  250. const finalImageList = await downloadSelectedLayerAsSvg(dirHandle, fileKey, layerList);
  251. const successText = getSuccessText(finalImageList);
  252. showSuccess(successText);
  253. } catch (e) {
  254. console.error(e);
  255. showError(e.toString());
  256. }
  257. }
  258.  
  259. /**
  260. * 将选中的图层下载为经 svgo 优化过后的 svg 图像,保存到指定地址
  261. * @async
  262. * @param dirHandle {FileSystemDirectoryHandle} 文件操作 Handle
  263. * @param fileKey {string} figma 文件 key
  264. * @param layerList {Image[]} 图层信息,格式为 [{"id": "svg id", "name": "svg name"}]
  265. * @return {Promise<Image[]>}
  266. */
  267. async function downloadSelectedLayerAsSvg(dirHandle, fileKey, layerList) {
  268. let optimizedImageList;
  269. // 1. 下载源 svg
  270. const imageList = await downloadImageFromFigma(fileKey, layerList, "svg", 1);
  271. if (imageList === undefined || imageList.length === 0) {
  272. throw new Error("从 figma 获取图片失败,请检查网络连接");
  273. }
  274. // 任何一张图层未下载成功,都判定整体失败
  275. if (!imageList.every(image => image.originalContent !== undefined)) {
  276. throw new Error("从 figma 下载图片内容失败,请检查网络连接");
  277. }
  278. // 2. 经 svgo 优化
  279. optimizedImageList = await optimizeSvg(imageList, svgPrecision);
  280. // 3. 保存到指定文件
  281. optimizedImageList.forEach(image => image.finalContent = image.processedContent);
  282. await saveImageWithDifferentDpiToDir(dirHandle, optimizedImageList);
  283. return optimizedImageList;
  284. }
  285.  
  286. /**
  287. *
  288. * @param imageList {Image[]}
  289. * @param precision {number}
  290. * @returns
  291. */
  292. async function optimizeSvg(imageList, precision) {
  293. try {
  294. const responsePromiseList = imageList.map(image =>
  295. fetch(`${svgOptimizerUrl}?precision=${precision}`, {
  296. method: "POST",
  297. headers: {"Content-Type": image.originalContent.type},
  298. body: image.originalContent
  299. })
  300. );
  301. return await requestAndFillResponseToImageList(imageList, responsePromiseList);
  302. } catch (e) {
  303. throw new Error("svg 优化失败,请检查是否开启优化服务器");
  304. }
  305. }
  306.  
  307. // SVGO 优化下载功能 END
  308.  
  309. // PNG 下载及转换功能 START
  310. async function onClickDownloadPng(convertToWebp) {
  311. const layerList = getSelectedLayerList();
  312. if (layerList.length === 0) {
  313. showError("未选择图层");
  314. return;
  315. }
  316. const fileKey = figma.fileKey;
  317. let dirHandleId;
  318. if (convertToWebp) {
  319. dirHandleId = `${fileKey}-webp`;
  320. } else {
  321. dirHandleId = `${fileKey}-png`;
  322. }
  323. const dirHandle = await unsafeWindow.showDirectoryPicker({id: dirHandleId, mode: "readwrite"});
  324. showExporting();
  325. const scaleList = await getScaleList(dirHandle);
  326. if (scaleList.length === 0) {
  327. showError("所选目录下需要有指定 dpi 的\"drawable-*dpi\"的文件夹");
  328. return;
  329. }
  330. try {
  331. const finalImageList = await exportPng(convertToWebp, dirHandle, fileKey, layerList, scaleList);
  332. let successText;
  333. if (!finalImageList.every(image => image.format === "png")) {
  334. // 表示有导出为 webp 的文件
  335. successText = getSuccessText(finalImageList);
  336. }
  337. showSuccess(successText);
  338. } catch (e) {
  339. console.error(e);
  340. showError(e.toString());
  341. }
  342. }
  343.  
  344. /**
  345. *
  346. * @param {boolean} convertToWebp 是否需要转换成 webp
  347. * @param {FileSystemDirectoryHandle} dirHandle
  348. * @param {string} fileKey figma 对应的文件 key
  349. * @param {Image[]} layerList 需要导出的图层信息,包括 id 和 name
  350. * @param {number[]} scaleList dpi 对应的缩放倍率
  351. * @returns {Promise<Image[]>}
  352. */
  353. async function exportPng(convertToWebp, dirHandle, fileKey, layerList, scaleList) {
  354. let imageList = await downloadSelectedLayerAsPng(dirHandle, fileKey, layerList, scaleList);
  355. if (convertToWebp) {
  356. imageList = await transferPngListToWebp(imageList, convertWebpQuality);
  357. imageList.forEach((image) => {
  358. // 只有在 webp 小于 png 时,才存储为 webp
  359. if (image.processedContent.size > image.originalContent.size) {
  360. image.format = "png";
  361. image.finalContent = image.originalContent;
  362. } else {
  363. image.format = "webp";
  364. image.finalContent = image.processedContent;
  365. }
  366. });
  367. } else {
  368. imageList.forEach((image) => {
  369. image.format = "png";
  370. image.finalContent = image.originalContent;
  371. });
  372. }
  373. await saveImageWithDifferentDpiToDir(dirHandle, imageList);
  374. return imageList;
  375. }
  376.  
  377. /**
  378. * 通过分析选中目录下的文件夹情况,得出需要下载的 dpi 对应的缩放倍率列表
  379. * @param {FileSystemDirectoryHandle} dirHandle
  380. * @return {Promise<number[]>}
  381. */
  382. async function getScaleList(dirHandle) {
  383. const scaleList = [];
  384. for await (const file of dirHandle.values()) {
  385. if (file.kind === "directory") {
  386. const scale = dirNameToScaleMap.get(file.name);
  387. if (scale !== undefined) {
  388. scaleList.push(scale);
  389. }
  390. }
  391. }
  392. return scaleList;
  393. }
  394.  
  395. /**
  396. * 将选中的图层根据给出的 scaleList 下载为 png
  397. * @param {FileSystemDirectoryHandle} dirHandle 文件操作 Handle
  398. * @param {string} fileKey figma 文件 key
  399. * @param {Image[]} layerList 图层信息,格式为 [{"id": "svg id", "name": "svg name"}]
  400. * @param {number[]} scaleList dpi 对应的缩放倍率
  401. * @return {Promise<Image[]>} 从 figma 下载下来的图片内容
  402. */
  403. async function downloadSelectedLayerAsPng(dirHandle, fileKey, layerList, scaleList) {
  404. const imageGroupByScale = await Promise.all(scaleList.map(scale => downloadImageFromFigma(fileKey, layerList, "png", scale)));
  405. /** @type {Image[]} */
  406. const imageList = imageGroupByScale.flat().filter(image => image !== undefined);
  407. if (imageList === undefined || imageList.length === 0) {
  408. throw new Error("从 figma 获取图片失败,请检查网络连接");
  409. }
  410. // 任何一张图层未下载成功,都判定整体失败
  411. if (!imageList.every(image => image.originalContent !== undefined)) {
  412. throw new Error("从 figma 下载图片内容失败,请检查网络连接");
  413. }
  414. return imageList;
  415. }
  416.  
  417. /**
  418. * 批量转换 png 为 webp
  419. * @param {Image[]} imageList
  420. * @param {number} quality 质量
  421. * @return {Promise<Image[]>} 输出的值比参数 imageList 添加了 processedContent 属性
  422. *
  423. * @throws {Error} 操作失败会抛出异常
  424. */
  425. async function transferPngListToWebp(imageList, quality) {
  426. try {
  427. const responsePromiseList = imageList.map(image =>
  428. fetch(`${pngConvertToWebpUrl}?quality=${quality}`, {
  429. method: "POST",
  430. headers: {"Content-Type": image.originalContent.type},
  431. body: image.originalContent
  432. })
  433. );
  434. return await requestAndFillResponseToImageList(imageList, responsePromiseList);
  435. } catch (e) {
  436. throw new Error("png 转 webp 操作失败,请检查是否开启优化服务器");
  437. }
  438. }
  439.  
  440. // PNG 下载及转换功能 END
  441.  
  442. // 公共能力 START
  443. /**
  444. * 获取当前选中的图层,包括 id 和 name
  445. * @return {[Image]}
  446. */
  447. function getSelectedLayerList() {
  448. return figma.currentPage.selection.map(node => new Image(node.id, node.name.replace(/[^a-z0-9_]/g, "")));
  449. }
  450.  
  451. /**
  452. * 生成的一个随机四位数,并以下划线开头,作为文件的前缀,以防重名时覆盖已有文件
  453. * @return {string}
  454. */
  455. function getRandomPrefix() {
  456. return "_" + Math.floor(Math.random() * 9000 + 1000);
  457. }
  458.  
  459. /**
  460. * 执行请求,将返回的内容填充到 imageList 中并返回
  461. * @param {Image[]} imageList
  462. * @param {Promise<Response>[]} responsePromiseList
  463. * @return {Promise<Image[]>}
  464. */
  465. async function requestAndFillResponseToImageList(imageList, responsePromiseList) {
  466. const webpBlobResponse = await Promise.all(responsePromiseList);
  467. if (webpBlobResponse.every(res => res.status === 200)) {
  468. for (const image of imageList) {
  469. const index = imageList.indexOf(image);
  470. image.processedContent = await webpBlobResponse[index].blob();
  471. }
  472. return imageList;
  473. } else {
  474. throw Error();
  475. }
  476. }
  477.  
  478. /**
  479. * 下载选中图层的内容,包括内容指向 url 和具体的文件内容
  480. * @async
  481. * @param {string} figmaFileKey
  482. * @param {string} format 格式 svg, png
  483. * @param {number} scale 缩放大小
  484. * @param {Image[]} layerList 包含有 id 和 name 的图层信息列表
  485. * @returns {Promise<Image[]>} 从 figma 下载下来的图片内容
  486. */
  487. async function downloadImageFromFigma(figmaFileKey, layerList, format, scale) {
  488. try {
  489. // 此处必须深拷贝
  490. const imageList = layerList.map(layer => new Image(layer.id, layer.name));
  491. const ids = imageList.map(image => image.id);
  492. let url = `https://api.figma.com/v1/images/${figmaFileKey}?ids=${ids.join(",")}&format=${format}&scale=${scale}`;
  493. const res = await fetch(url,
  494. {
  495. headers: {
  496. "X-FIGMA-TOKEN": figmaToken
  497. }
  498. }
  499. );
  500. if (res.status !== 200) return undefined;
  501. const originalImageListJson = await res.json();
  502. imageList.forEach(layer => {
  503. layer.url = originalImageListJson.images[layer.id];
  504. layer.scale = scale;
  505. layer.format = format;
  506. });
  507. // 下载 image 内容
  508. const originalContentList = await Promise.all(imageList.map(image => downloadOriginalImageContent(image.url)));
  509. originalContentList.forEach((originalContent, index) => {
  510. imageList[index].originalContent = originalContent;
  511. });
  512. return imageList;
  513. } catch (e) {
  514. console.error(e);
  515. }
  516. }
  517.  
  518. /**
  519. * 下载给定的 url 的内容
  520. * @async
  521. * @param url 资源目标 url
  522. * @returns {Promise<Blob>} 下载下来的二进制内容
  523. */
  524. async function downloadOriginalImageContent(url) {
  525. try {
  526. let res = await fetch(url);
  527. if (res.status === 200) {
  528. // 需要用二进制数据
  529. return await res.blob();
  530. } else {
  531. console.log("错误?" + res.status);
  532. }
  533. } catch (e) {
  534. console.error(e);
  535. }
  536. }
  537.  
  538. /**
  539. * 保存内容到文件
  540. * @param {FileSystemDirectoryHandle} dirHandle
  541. * @param {Image[]} imageList
  542. */
  543. async function saveImageWithDifferentDpiToDir(dirHandle, imageList) {
  544. const prefix = getRandomPrefix();
  545. for (const image of imageList) {
  546. /** @type {FileSystemDirectoryHandle} */
  547. let drawableDirHandle;
  548. if (image.format === "svg") {
  549. // svg 图片直接保存到目录下
  550. drawableDirHandle = dirHandle;
  551. } else {
  552. // 其它图片需要保存到对应 dpi 的目录下
  553. const drawableDirName = scaleToDirNameMap.get(image.scale);
  554. drawableDirHandle = await dirHandle.getDirectoryHandle(drawableDirName);
  555. }
  556. const fileHandle = await drawableDirHandle.getFileHandle(`${prefix}_${image.name}.${image.format}`, {create: true});
  557. const writable = await fileHandle.createWritable();
  558. await writable.write(image.finalContent);
  559. await writable.close();
  560. }
  561. }
  562.  
  563. /**
  564. * 格式化 bytes 数量为可读字符串
  565. * @param {number} bytesSize
  566. * @return {string}
  567. */
  568. function formatBytes(bytesSize) {
  569. if (bytesSize < 1024) {
  570. return bytesSize + " Bytes";
  571. } else if (bytesSize < 1024 * 1024) {
  572. return (bytesSize / 1024).toFixed(2) + " KB";
  573. } else {
  574. return (bytesSize / (1024 * 1024)).toFixed(2) + " MB";
  575. }
  576. }
  577.  
  578. /**
  579. * 获取成功提示文字,主要是关于体积缩减大小
  580. * @param {Image[]} finalImageList
  581. * @return {string}
  582. */
  583. function getSuccessText(finalImageList) {
  584. const originalSize = finalImageList.reduce((accumulator, currentValue) => {
  585. return accumulator + currentValue.originalContent.size;
  586. }, 0);
  587. const finalSize = finalImageList.reduce((accumulator, currentValue) => {
  588. return accumulator + currentValue.finalContent.size;
  589. }, 0);
  590. return `成功缩减体积 ${formatBytes(originalSize - finalSize)}(${((originalSize - finalSize) * 100 / originalSize).toFixed(0)}%)`;
  591. }
  592.  
  593. function showExporting() {
  594. Toast.fire({
  595. title: "图层导出中...",
  596. didOpen() {
  597. Swal.showLoading();
  598. }
  599. });
  600. }
  601.  
  602. /**
  603. * @param {string} successText
  604. */
  605. function showSuccess(successText) {
  606. Toast.fire({
  607. icon: "success",
  608. title: "导出成功",
  609. text: successText
  610. });
  611. }
  612.  
  613. /**
  614. * @param {string} errorText
  615. */
  616. function showError(errorText) {
  617. Toast.fire({
  618. icon: "error",
  619. text: errorText,
  620. title: "导出失败,请重试",
  621. });
  622. }
  623.  
  624. // 公共能力 END
  625.  
  626. })();