mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-04-22 21:59:49 +08:00
♻️ 重构图像保存逻辑,完善错误处理
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}`);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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}`);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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}`);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user