♻️ 重构图像保存逻辑,完善错误处理

This commit is contained in:
BTMuli
2026-04-08 18:16:44 +08:00
parent 7bfa5df594
commit e133135c8f
11 changed files with 218 additions and 107 deletions

View File

@@ -15,7 +15,7 @@
import useAppStore from "@store/app.js";
import { onMounted, onUnmounted, ref, watch } from "vue";
import { saveImgLocal } from "@/utils/TGShare.js";
import { saveImgBlob } from "@/utils/TGShare.js";
type TMiImgProps = {
src: string;
@@ -31,6 +31,7 @@ const emits = defineEmits<TMiImgEmits>();
const appStore = useAppStore();
const localUrl = ref<string>();
const link = ref<string>(getRemoteLink());
onMounted(async () => {
if (!props.src) return;
@@ -38,24 +39,30 @@ onMounted(async () => {
localUrl.value = props.src;
return;
}
const link = props.ori ? props.src : appStore.getImageUrl(props.src);
localUrl.value = await saveImgLocal(link);
link.value = getRemoteLink();
localUrl.value = await saveImgBlob(link.value);
});
function getRemoteLink(): string {
return props.ori ? props.src : appStore.getImageUrl(props.src);
}
watch(
() => props.src,
async () => {
if (!props.src) return;
if (localUrl.value) URL.revokeObjectURL(localUrl.value);
if (localUrl.value && localUrl.value !== link.value) URL.revokeObjectURL(localUrl.value);
localUrl.value = undefined;
const link = props.ori ? props.src : appStore.getImageUrl(props.src);
if (!props.src.startsWith("http")) localUrl.value = props.src;
else localUrl.value = await saveImgLocal(link);
else {
link.value = getRemoteLink();
localUrl.value = await saveImgBlob(link.value);
}
},
);
onUnmounted(() => {
if (localUrl.value) URL.revokeObjectURL(localUrl.value);
if (localUrl.value && localUrl.value !== link.value) URL.revokeObjectURL(localUrl.value);
});
</script>
<style lang="scss" scoped>

View File

@@ -61,9 +61,11 @@ import TOverlay from "@comp/app/t-overlay.vue";
import showSnackbar from "@comp/func/snackbar.js";
import { fetch } from "@tauri-apps/plugin-http";
import { parseBirthGal, parseBirthSrc } from "@utils/birthParser.js";
import { copyToClipboard, getImageBuffer, saveCanvasImg } from "@utils/TGShare.js";
import { copyToClipboard, saveBufferFile } from "@utils/TGShare.js";
import { bytesToSize } from "@utils/toolFunc.js";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import TGHttps from "@utils/TGHttps.js";
import TGLogger from "@utils/TGLogger.js";
type ToArcBirthProps = { data?: TGApp.Archive.Birth.DrawItem; choice: boolean };
@@ -96,10 +98,26 @@ async function clearData(): Promise<void> {
if (showText.value) await loadText();
}
async function loadImageBuffer(): Promise<void> {
if (buffer.value !== null || !props.data) return;
const imageUrl = props.data.take_picture[Number(props.choice)];
try {
buffer.value = await TGHttps.buffer(imageUrl);
} catch (e) {
let errMsg = String(e);
if (TGHttps.isHttpErr(e)) {
errMsg = e.status ? `[${e.status}] ${e.statusText}` : e.message;
}
showSnackbar.error(`获取图像Buffer失败${errMsg}`);
await TGLogger.Error(`[PaoBirthCard][loadImageBuffer] 获取图像Buffer失败${imageUrl}`);
await TGLogger.Error(`[PaoBirthCard][loadImageBuffer] ${e}`);
}
}
async function onCopy(): Promise<void> {
if (!props.data) return;
const image = props.data.take_picture[Number(props.choice)];
if (buffer.value === null) buffer.value = await getImageBuffer(image);
await loadImageBuffer();
if (buffer.value === null) return;
const size = bytesToSize(buffer.value.byteLength);
await copyToClipboard(buffer.value);
showSnackbar.success(`图片已复制到剪贴板,大小:${size}`);
@@ -107,11 +125,9 @@ async function onCopy(): Promise<void> {
async function onDownload(): Promise<void> {
if (!props.data) return;
const image = props.data.take_picture[Number(props.choice)];
if (buffer.value === null) buffer.value = await getImageBuffer(image);
const size = bytesToSize(buffer.value.byteLength);
await saveCanvasImg(buffer.value, Date.now().toString());
showSnackbar.success(`图片已下载到本地,大小:${size}`);
await loadImageBuffer();
if (buffer.value === null) return;
await saveBufferFile(buffer.value, Date.now().toString());
}
function showComments(): void {

View File

@@ -1,20 +1,20 @@
<!-- 祈愿日历图表组件 -->
<template>
<v-chart
ref="chartRef"
class="gro-chart-calendar"
:option="chartOptions"
autoresize
:theme="echartsTheme"
:init-options="{ locale: 'ZH' }"
:style="{ height: chartHeight }"
:key="`gro-chart-calendar-${echartsTheme}`"
ref="chartRef"
:init-options="{ locale: 'ZH' }"
:option="chartOptions"
:style="{ height: chartHeight }"
:theme="echartsTheme"
autoresize
class="gro-chart-calendar"
/>
</template>
<script lang="ts" setup>
import TSUserGacha from "@Sqlm/userGacha.js";
import useAppStore from "@store/app.js";
import { getImageBuffer, saveCanvasImg } from "@utils/TGShare.js";
import { saveImgFile } from "@utils/TGShare.js";
import type { HeatmapSeriesOption } from "echarts/charts.js";
import { HeatmapChart } from "echarts/charts.js";
import type {
@@ -113,8 +113,7 @@ async function getCalendarOptions(): Promise<EChartsOption> {
backgroundColor: theme.value === "dark" ? "#2c343c" : "#ffffff",
excludeComponents: ["toolbox"],
});
const buffer = await getImageBuffer(dataUrl);
await saveCanvasImg(buffer, `gacha-chart-calendar-${props.uid}`);
await saveImgFile(dataUrl, `gacha-chart-calendar-${props.uid}`);
},
},
},

View File

@@ -1,18 +1,18 @@
<template>
<v-chart
ref="chartRef"
class="gro-chart-overview"
:option="chartOptions"
autoresize
:theme="echartsTheme"
:init-options="{ locale: 'ZH' }"
:key="`gro-chart-overview-${echartsTheme}`"
ref="chartRef"
:init-options="{ locale: 'ZH' }"
:option="chartOptions"
:theme="echartsTheme"
autoresize
class="gro-chart-overview"
/>
</template>
<script lang="ts" setup>
import TSUserGacha from "@Sqlm/userGacha.js";
import useAppStore from "@store/app.js";
import { getImageBuffer, saveCanvasImg } from "@utils/TGShare.js";
import { saveImgFile } from "@utils/TGShare.js";
import type { PieSeriesOption } from "echarts/charts.js";
import { BarChart, PieChart } from "echarts/charts.js";
import type {
@@ -101,8 +101,7 @@ async function getOverviewOptions(): Promise<EChartsOption> {
backgroundColor: theme.value === "dark" ? "#2c343c" : "#ffffff",
excludeComponents: ["toolbox"],
});
const buffer = await getImageBuffer(dataUrl);
await saveCanvasImg(buffer, `gacha-overview-${props.uid}`);
await saveImgFile(dataUrl, `gacha-overview-${props.uid}`);
},
},
},

View File

@@ -1,19 +1,19 @@
<!-- 祈愿柱状图组件 -->
<template>
<v-chart
ref="chartRef"
class="gro-chart-stackbar"
:option="chartOptions"
autoresize
:theme="echartsTheme"
:init-options="{ locale: 'ZH' }"
:key="`gro-chart-stackbar-${echartsTheme}`"
ref="chartRef"
:init-options="{ locale: 'ZH' }"
:option="chartOptions"
:theme="echartsTheme"
autoresize
class="gro-chart-stackbar"
/>
</template>
<script lang="ts" setup>
import TSUserGacha from "@Sqlm/userGacha.js";
import useAppStore from "@store/app.js";
import { getImageBuffer, saveCanvasImg } from "@utils/TGShare.js";
import { saveImgFile } from "@utils/TGShare.js";
import type { BarSeriesOption } from "echarts/charts.js";
import { BarChart } from "echarts/charts.js";
import type {
@@ -146,8 +146,7 @@ async function getStackBarOptions(): Promise<EChartsOption> {
backgroundColor: theme.value === "dark" ? "#2c343c" : "#ffffff",
excludeComponents: ["toolbox"],
});
const buffer = await getImageBuffer(dataUrl);
await saveCanvasImg(buffer, `gacha-overview-${props.uid}`);
await saveImgFile(dataUrl, `gacha-overview-${props.uid}`);
},
},
},

View File

@@ -24,7 +24,7 @@
/>
</template>
<script lang="ts" setup>
import { saveImgLocal } from "@utils/TGShare.js";
import { saveImgBlob } from "@utils/TGShare.js";
import { computed, nextTick, onMounted, onUnmounted, ref, useTemplateRef } from "vue";
import type { TpImage } from "./tp-image.vue";
@@ -71,6 +71,7 @@ type TpEmoticonProps = {
const props = defineProps<TpEmoticonProps>();
const localUrl = ref<string>();
const linkUrl = ref<string>(props.data.insert.custom_emoticon.url);
const showOverlay = ref<boolean>(false);
const bgColor = ref<string>("transparent");
const loadErr = ref<boolean>(false);
@@ -93,11 +94,12 @@ const showLabel = computed<boolean>(() => {
console.log("tp-emoticon", props.data.insert.custom_emoticon);
onMounted(async () => {
localUrl.value = await saveImgLocal(props.data.insert.custom_emoticon.url);
linkUrl.value = props.data.insert.custom_emoticon.url;
localUrl.value = await saveImgBlob(linkUrl.value);
});
onUnmounted(() => {
if (localUrl.value) URL.revokeObjectURL(localUrl.value);
if (localUrl.value && localUrl.value !== linkUrl.value) URL.revokeObjectURL(localUrl.value);
});
function getImageExt(): string {
@@ -109,7 +111,8 @@ async function handleEmoticonClick(): Promise<void> {
if (loadErr.value) {
localUrl.value = undefined;
await nextTick();
localUrl.value = await saveImgLocal(props.data.insert.custom_emoticon.url);
linkUrl.value = props.data.insert.custom_emoticon.url;
localUrl.value = await saveImgBlob(linkUrl.value);
loadErr.value = false;
return;
}

View File

@@ -1,34 +1,34 @@
<!-- TODO: 链接处理重构 -->
<template>
<div class="tp-image-box" v-if="localUrl !== undefined">
<img :src="localUrl" @click="showOverlay = true" :alt="oriUrl" :title="getImageTitle()" />
<div v-if="localUrl !== undefined" class="tp-image-box">
<img :alt="oriUrl" :src="localUrl" :title="getImageTitle()" @click="showOverlay = true" />
<div
class="act"
@click.stop="showOri = true"
title="查看原图"
v-if="!showOri"
class="act"
data-html2canvas-ignore
title="查看原图"
@click.stop="showOri = true"
>
<v-icon size="16" color="white">mdi-magnify</v-icon>
<v-icon color="white" size="16">mdi-magnify</v-icon>
</div>
</div>
<div v-else class="tp-image-load" :title="oriUrl">
<div v-else :title="oriUrl" class="tp-image-load">
<v-progress-circular :indeterminate="true" color="blue" size="small" />
<span>加载中...</span>
</div>
<VpOverlayImage
:image="props.data"
v-model="showOverlay"
v-model:link="localUrl"
v-model:ori="showOri"
v-model:bgColor="bgColor"
v-model:format="imgExt"
v-model:link="localUrl"
v-model:ori="showOri"
:image="props.data"
/>
</template>
<script lang="ts" setup>
import showLoading from "@comp/func/loading.js";
import useAppStore from "@store/app.js";
import { saveImgLocal } from "@utils/TGShare.js";
import { saveImgBlob } from "@utils/TGShare.js";
import { bytesToSize } from "@utils/toolFunc.js";
import { storeToRefs } from "pinia";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
@@ -53,9 +53,10 @@ const props = defineProps<TpImageProps>();
const showOverlay = ref<boolean>(false);
const localUrl = ref<string>();
const linkUrl = ref<string>("");
const bgColor = ref<string>("transparent");
const oriUrl = ref<string>("");
const oriUrl = ref<string>(miniImgUrl());
const imgExt = computed<string>(() => getImageExt());
const showOri = ref<boolean>(imgExt.value === "gif" || imageQualityPercent.value === 100);
@@ -69,8 +70,8 @@ console.log("tp-image", props.data.insert.image, props.data.attributes);
onMounted(async () => {
oriUrl.value = miniImgUrl();
const link = appStore.getImageUrl(oriUrl.value, imgExt.value);
localUrl.value = await saveImgLocal(link);
linkUrl.value = getLinkUrl();
localUrl.value = await saveImgBlob(linkUrl.value);
});
watch(
@@ -81,14 +82,14 @@ watch(
if (localUrl.value) URL.revokeObjectURL(localUrl.value);
const ext = getImageExt();
if (!["png", "jpg", "jpeg", "gif", "webp"].includes(ext.toLowerCase())) {
localUrl.value = await saveImgLocal(`${oriUrl.value}?format=jpg`);
} else localUrl.value = await saveImgLocal(oriUrl.value);
localUrl.value = await saveImgBlob(`${oriUrl.value}?format=jpg`);
} else localUrl.value = await saveImgBlob(oriUrl.value);
await showLoading.end();
},
);
onUnmounted(() => {
if (localUrl.value) URL.revokeObjectURL(localUrl.value);
if (localUrl.value && localUrl.value !== linkUrl.value) URL.revokeObjectURL(localUrl.value);
});
function miniImgUrl(): string {
@@ -102,6 +103,10 @@ function miniImgUrl(): string {
return `${link.origin}${link.pathname}`;
}
function getLinkUrl(): string {
return appStore.getImageUrl(oriUrl.value, imgExt.value);
}
function getImageTitle(): string {
const res: Array<string> = [];
if (props.data.attributes) {

View File

@@ -1,9 +1,9 @@
<template>
<div class="tp-video-box" v-if="videoData">
<div v-if="videoData" class="tp-video-box">
<iframe
class="tp-video-container"
:src="props.data.insert.video"
:id="`tp-video-${props.data.insert.video}`"
:allowfullscreen="true"
:src="props.data.insert.video"
allow="
accelerometer;
autoplay;
@@ -12,12 +12,12 @@
gyroscope;
picture-in-picture;
"
class="tp-video-container"
sandbox="allow-forms allow-same-origin allow-popups allow-presentation allow-scripts"
:id="`tp-video-${props.data.insert.video}`"
/>
<div class="tp-video-share">
<img alt="cover" :src="videoCover" class="tp-video-cover" />
<img alt="icon" src="/UI/post/video_play_bili.webp" class="tp-video-icon" />
<img :src="videoCover" alt="cover" class="tp-video-cover" />
<img alt="icon" class="tp-video-icon" src="/UI/post/video_play_bili.webp" />
<div class="tp-video-info">
<span>{{ videoData.bvid }}|{{ timestampToDate(videoData.ctime * 1000) }}</span>
<span>{{ videoData.title }}</span>
@@ -36,7 +36,7 @@
<script lang="ts" setup>
import Bili from "@Bili/index.js";
import showSnackbar from "@comp/func/snackbar.js";
import { saveImgLocal } from "@utils/TGShare.js";
import { saveImgBlob } from "@utils/TGShare.js";
import { getVideoDuration, timestampToDate } from "@utils/toolFunc.js";
import { onMounted, onUnmounted, ref, shallowRef } from "vue";
@@ -46,6 +46,7 @@ type TpVideoProps = { data: TpVideo };
const props = defineProps<TpVideoProps>();
const videoAspectRatio = ref<number>(16 / 9);
const videoCover = ref<string>();
const videoCoverLink = ref<string>();
const videoData = shallowRef<TGApp.Plugins.Bili.Video.ViewData>();
console.log("tpVideo", props.data.insert.video);
@@ -63,11 +64,14 @@ onMounted(async () => {
const meta = videoData.value.dimension;
if (meta.width > meta.height) videoAspectRatio.value = meta.width / meta.height;
else videoAspectRatio.value = meta.height / meta.width;
videoCover.value = await saveImgLocal(videoData.value.pic);
videoCoverLink.value = videoData.value.pic;
videoCover.value = await saveImgBlob(videoCoverLink.value);
});
onUnmounted(() => {
if (videoCover.value) URL.revokeObjectURL(videoCover.value);
if (videoCover.value && videoCover.value !== videoCoverLink.value) {
URL.revokeObjectURL(videoCover.value);
}
});
</script>
<style lang="css" scoped>

View File

@@ -7,7 +7,7 @@
data-html2canvas-ignore
></div>
<div class="tp-vod-share">
<img v-if="coverUrl" :src="coverUrl" alt="cover" class="tp-vod-cover" />
<img v-if="coverLocal" :src="coverLocal" alt="cover" class="tp-vod-cover" />
<v-progress-circular v-else color="blue" indeterminate size="25" />
<img alt="icon" class="tp-vod-icon" src="/UI/post/video_play.svg" />
<div class="tp-vod-time">
@@ -26,11 +26,14 @@ import showLoading from "@comp/func/loading.js";
import useAppStore from "@store/app.js";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { openUrl } from "@tauri-apps/plugin-opener";
import { getImageBuffer, saveCanvasImg, saveImgLocal } from "@utils/TGShare.js";
import { saveBufferFile, saveImgBlob } from "@utils/TGShare.js";
import { getVideoDuration } from "@utils/toolFunc.js";
import Artplayer, { type Option } from "artplayer";
import { storeToRefs } from "pinia";
import { onMounted, onUnmounted, ref, shallowRef, toRaw, watch } from "vue";
import TGHttps from "@utils/TGHttps.js";
import showSnackbar from "@comp/func/snackbar.js";
import TGLogger from "@utils/TGLogger.js";
type TpVod = {
insert: {
@@ -61,7 +64,8 @@ const props = defineProps<TpVodProps>();
const { postViewWide } = storeToRefs(appStore);
const coverUrl = ref<string>();
const coverLocal = ref<string>();
const coverLink = ref<string>(getCoverLink());
const vodAspectRatio = ref<number>(16 / 9);
const coverBuffer = shallowRef<ArrayBuffer | null>(null);
const container = shallowRef<Artplayer | null>(null);
@@ -79,13 +83,13 @@ onMounted(async () => {
prev.size > curr.size ? prev : curr,
);
vodAspectRatio.value = calcAspectRatio();
const localUrl = appStore.getImageUrl(props.data.insert.vod.cover);
coverUrl.value = await saveImgLocal(localUrl);
coverLink.value = getCoverLink();
coverLocal.value = await saveImgBlob(coverLink.value);
const option: Option = {
id: props.data.insert.vod.id,
container: `#tp-vod-${props.data.insert.vod.id}`,
url: highestResolution.url,
poster: coverUrl.value,
poster: coverLocal.value,
type: highestResolution.format,
playbackRate: true,
aspectRatio: false,
@@ -122,10 +126,12 @@ onMounted(async () => {
tooltip: "下载封面",
click: async () => {
await showLoading.start("正在下载封面", props.data.insert.vod.cover);
if (!coverBuffer.value) {
coverBuffer.value = await getImageBuffer(props.data.insert.vod.cover);
await loadCoverBuffer();
if (coverBuffer.value === null) {
await showLoading.end();
return;
}
await saveCanvasImg(coverBuffer.value, `vod-cover-${props.data.insert.vod.id}`);
await saveBufferFile(coverBuffer.value, `vod-cover-${props.data.insert.vod.id}`);
await showLoading.end();
},
},
@@ -146,6 +152,27 @@ onMounted(async () => {
container.value?.on("fullscreen", async (s) => await getCurrentWindow().setFullscreen(s));
});
function getCoverLink(): string {
return appStore.getImageUrl(props.data.insert.vod.cover);
}
async function loadCoverBuffer(): Promise<void> {
if (coverBuffer.value !== null) return;
try {
coverBuffer.value = await TGHttps.buffer(props.data.insert.vod.cover);
} catch (e) {
let errMsg = String(e);
if (TGHttps.isHttpErr(e)) {
errMsg = e.status ? `[${e.status}] ${e.statusText}` : e.message;
}
showSnackbar.error(`获取图像Buffer失败${errMsg}`);
await TGLogger.Error(
`[TpVod][loadCoverBuffer] 获取图像Buffer失败${props.data.insert.vod.cover}`,
);
await TGLogger.Error(`[TpVod][loadCoverBuffer] ${e}`);
}
}
function calcAspectRatio(): number {
const resolutions = props.data.insert.vod.resolutions;
const highestResolution = resolutions.reduce((prev, curr) =>
@@ -161,7 +188,9 @@ function calcAspectRatio(): number {
onUnmounted(() => {
container.value?.destroy();
if (coverBuffer.value) coverBuffer.value = null;
if (coverUrl.value) URL.revokeObjectURL(coverUrl.value);
if (coverLocal.value && coverLocal.value !== coverLink.value) {
URL.revokeObjectURL(coverLocal.value);
}
});
</script>
<style lang="css" scoped>

View File

@@ -87,7 +87,7 @@
import TOverlay from "@comp/app/t-overlay.vue";
import showLoading from "@comp/func/loading.js";
import showSnackbar from "@comp/func/snackbar.js";
import { copyToClipboard, getImageBuffer, saveCanvasImg } from "@utils/TGShare.js";
import { copyToClipboard, saveBufferFile } from "@utils/TGShare.js";
import { bytesToSize } from "@utils/toolFunc.js";
import {
computed,
@@ -102,6 +102,8 @@ import {
} from "vue";
import type { TpImage } from "./tp-image.vue";
import TGHttps from "@utils/TGHttps.js";
import TGLogger from "@utils/TGLogger.js";
type TpoImageProps = { image: TpImage };
type VpoiSize = { width: number; height: number };
@@ -397,13 +399,32 @@ function setBlackBg(): void {
bgColor.value = bgMode.value === 0 ? "transparent" : bgMode.value === 1 ? "black" : "white";
}
async function loadImageBuffer(): Promise<void> {
if (buffer.value !== null) return;
try {
buffer.value = await TGHttps.buffer(oriLink.value);
} catch (e) {
let errMsg = String(e);
if (TGHttps.isHttpErr(e)) {
errMsg = e.status ? `[${e.status}] ${e.statusText}` : e.message;
}
showSnackbar.error(`获取图像Buffer失败${errMsg}`);
await TGLogger.Error(`[VpOverlayImage][loadImageBuffer] 获取图像Buffer失败${oriLink.value}`);
await TGLogger.Error(`[VpOverlayImage][loadImageBuffer] ${e}`);
}
}
async function onCopy(): Promise<void> {
if (!showOri.value) {
showOri.value = true;
await nextTick();
}
await showLoading.start("正在复制图片到剪贴板");
if (buffer.value === null) buffer.value = await getImageBuffer(oriLink.value);
await loadImageBuffer();
if (buffer.value === null) {
await showLoading.end();
return;
}
const size = bytesToSize(buffer.value.byteLength);
await copyToClipboard(buffer.value);
await showLoading.end();
@@ -416,14 +437,19 @@ async function onDownload(): Promise<void> {
await nextTick();
}
await showLoading.start("正在下载图片到本地", oriLink.value);
if (buffer.value === null) buffer.value = await getImageBuffer(oriLink.value);
await loadImageBuffer();
if (buffer.value === null) {
await showLoading.end();
return;
}
if (buffer.value.byteLength > MAX_FILE_SIZE) {
await showLoading.end();
showSnackbar.warn("图片过大,无法下载到本地");
return;
}
let fileName = oriLink.value.split("/").pop()?.split(".")[0];
if (fileName === undefined) fileName = Date.now().toString();
await saveCanvasImg(buffer.value, fileName, format.value);
await saveBufferFile(buffer.value, fileName, format.value);
await showLoading.end();
}
</script>

View File

@@ -19,13 +19,13 @@ import { bytesToSize } from "./toolFunc.js";
/**
* 保存图片-canvas
* @since Beta v0.9.0
* @since Beta v0.10.0
* @param buffer - 图片数据
* @param filename - 文件名
* @param format - 文件格式
* @returns 无返回值
*/
export async function saveCanvasImg(
export async function saveBufferFile(
buffer: ArrayBuffer,
filename: string,
format?: string,
@@ -48,6 +48,31 @@ export async function saveCanvasImg(
showSnackbar.success(`已将 ${realName} 保存到本地,大小为 ${bytesToSize(bf.length)}`);
}
/**
* 从远程获取buffer并保存到本地文件
* @since Beta v0.10.0
* @param url - 图片链接
* @param fn - 文件名
* @returns 保存结果
*/
export async function saveImgFile(url: string, fn: string, fmt?: string): Promise<void> {
let buffer: ArrayBuffer | undefined;
try {
buffer = await TGHttps.buffer(url);
} catch (e) {
let errMsg = String(e);
if (TGHttps.isHttpErr(e)) {
errMsg = e.status ? `[${e.status}] ${e.statusText}` : e.message;
}
showSnackbar.error(`获取图像Buffer失败${errMsg}`);
await TGLogger.Error(`[TGShare][saveImgFile] 获取图像Buffer失败${url}`);
await TGLogger.Error(`${e}`);
return;
}
if (buffer === undefined) return;
await saveBufferFile(buffer, fn, fmt);
}
/**
* 将图片保存到本地
* @since Beta v0.10.0
@@ -55,24 +80,23 @@ export async function saveCanvasImg(
* @param url - 图片链接
* @returns 图片元素
*/
export async function saveImgLocal(url: string): Promise<string> {
const res = await TGHttps.buffer(url);
const buffer = new Uint8Array(res);
const blob = new Blob([buffer], { type: "image/png" });
export async function saveImgBlob(url: string): Promise<string> {
let buffer: ArrayBuffer | undefined;
try {
buffer = await TGHttps.buffer(url);
} catch (e) {
let errMsg = String(e);
if (TGHttps.isHttpErr(e)) {
errMsg = e.status ? `[${e.status}] ${e.statusText}` : e.message;
}
await TGLogger.Error(`[TGShare][saveImgBlob] 获取图像Buffer失败: ${url}`);
await TGLogger.Error(`[TGShare][saveImgBlob] ${errMsg}`);
return url;
}
const blob = new Blob([new Uint8Array(buffer)], { type: "image/png" });
return URL.createObjectURL(blob);
}
/**
* 返回图片 buffer
* @since Beta v0.10.0
* @deprecated 使用 TGHttps.buffer
* @param url - 图片链接
* @returns 图片 buffer
*/
export async function getImageBuffer(url: string): Promise<ArrayBuffer> {
return await TGHttp.buffer(url);
}
/**
* 获取分享截图背景色
* @since Beta v0.9.0
@@ -138,11 +162,11 @@ export async function generateShareImg(
await TGLogger.Info(`[generateShareImg][${fileName}] 图像大小为 ${sizeStr}`);
const { shareDefaultFile } = storeToRefs(useAppStore());
if (shareDefaultFile.value === 0) {
await saveCanvasImg(bf.buffer, fileName);
await saveBufferFile(bf.buffer, fileName);
return;
}
if (typeof shareDefaultFile.value === "number" && size > shareDefaultFile.value * 1024 * 1024) {
await saveCanvasImg(bf.buffer, fileName);
await saveBufferFile(bf.buffer, fileName);
return;
}
try {
@@ -151,7 +175,7 @@ export async function generateShareImg(
await TGLogger.Info(`[generateShareImg][${fileName}] 已将图像复制到剪贴板`);
} catch (e) {
await TGLogger.Error(`[generateShareImg][${fileName}] 复制到剪贴板失败 ${e}`);
await saveCanvasImg(bf.buffer, fileName);
await saveBufferFile(bf.buffer, fileName);
}
}