mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2026-03-19 04:29:45 +08:00
🐛 重构数据解析,修复HEIC格式图片渲染异常
This commit is contained in:
@@ -1,11 +1,6 @@
|
||||
<template>
|
||||
<div class="tp-image-box" v-if="localUrl !== undefined">
|
||||
<img
|
||||
:src="localUrl"
|
||||
@click="showOverlay = true"
|
||||
:alt="props.data.insert.image"
|
||||
:title="getImageTitle()"
|
||||
/>
|
||||
<img :src="localUrl" @click="showOverlay = true" :alt="oriUrl" :title="getImageTitle()" />
|
||||
<div
|
||||
class="act"
|
||||
@click.stop="showOri = true"
|
||||
@@ -16,7 +11,7 @@
|
||||
<v-icon size="16" color="white">mdi-magnify</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="tp-image-load" :title="props.data.insert.image">
|
||||
<div v-else class="tp-image-load" :title="oriUrl">
|
||||
<v-progress-circular :indeterminate="true" color="primary" size="small" />
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
@@ -26,6 +21,7 @@
|
||||
v-model:link="localUrl"
|
||||
v-model:ori="showOri"
|
||||
v-model:bgColor="bgColor"
|
||||
v-model:format="imgExt"
|
||||
/>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@@ -39,7 +35,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from "vue";
|
||||
import VpOverlayImage from "./vp-overlay-image.vue";
|
||||
|
||||
export type TpImage = {
|
||||
insert: { image: string };
|
||||
insert: { image: string | TGApp.BBS.Post.Image };
|
||||
attributes?: {
|
||||
width: number;
|
||||
height: number;
|
||||
@@ -54,12 +50,17 @@ const appStore = useAppStore();
|
||||
const { imageQualityPercent } = storeToRefs(appStore);
|
||||
const props = defineProps<TpImageProps>();
|
||||
const showOverlay = ref<boolean>(false);
|
||||
const showOri = ref<boolean>(
|
||||
props.data.insert.image.endsWith(".gif") || imageQualityPercent.value === 100,
|
||||
);
|
||||
|
||||
const localUrl = ref<string>();
|
||||
const bgColor = ref<string>("transparent");
|
||||
|
||||
const oriUrl = computed<string>(() => {
|
||||
if (typeof props.data.insert.image === "string") return props.data.insert.image;
|
||||
return props.data.insert.image.url;
|
||||
});
|
||||
const imgExt = computed<string>(() => getImageExt());
|
||||
const showOri = ref<boolean>(imgExt.value === "gif" || imageQualityPercent.value === 100);
|
||||
|
||||
const imgWidth = computed<string>(() => {
|
||||
if (props.data.attributes === undefined) return "auto";
|
||||
if (props.data.attributes.width >= 690) return "100%";
|
||||
@@ -69,7 +70,7 @@ const imgWidth = computed<string>(() => {
|
||||
console.log("tp-image", props.data.insert.image, props.data.attributes);
|
||||
|
||||
onMounted(async () => {
|
||||
const link = appStore.getImageUrl(props.data.insert.image);
|
||||
const link = appStore.getImageUrl(oriUrl.value, imgExt.value);
|
||||
localUrl.value = await saveImgLocal(link);
|
||||
});
|
||||
|
||||
@@ -77,9 +78,12 @@ watch(
|
||||
() => showOri.value,
|
||||
async () => {
|
||||
if (!showOri.value) return;
|
||||
await showLoading.start("正在加载原图", props.data.insert.image);
|
||||
await showLoading.start("正在加载原图", oriUrl.value);
|
||||
if (localUrl.value) URL.revokeObjectURL(localUrl.value);
|
||||
localUrl.value = await saveImgLocal(props.data.insert.image);
|
||||
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);
|
||||
await showLoading.end();
|
||||
},
|
||||
);
|
||||
@@ -89,18 +93,39 @@ onUnmounted(() => {
|
||||
});
|
||||
|
||||
function getImageTitle(): string {
|
||||
if (props.data.attributes == undefined) return "";
|
||||
const res: string[] = [];
|
||||
res.push(`宽度:${props.data.attributes.width}px`);
|
||||
res.push(`高度:${props.data.attributes.height}px`);
|
||||
if (props.data.attributes.size) {
|
||||
const size = bytesToSize(props.data.attributes.size);
|
||||
res.push(`大小:${size}`);
|
||||
if (props.data.attributes) {
|
||||
res.push(`宽度:${props.data.attributes.width}px`);
|
||||
res.push(`高度:${props.data.attributes.height}px`);
|
||||
if (props.data.attributes.size) {
|
||||
const size = bytesToSize(props.data.attributes.size);
|
||||
res.push(`大小:${size}`);
|
||||
}
|
||||
res.push(`格式:${getImageExt()}`);
|
||||
return res.join("\n");
|
||||
}
|
||||
if (props.data.attributes.ext) {
|
||||
res.push(`格式:${props.data.attributes.ext}`);
|
||||
if (typeof props.data.insert.image !== "string") {
|
||||
res.push(`宽度:${props.data.insert.image.width}px`);
|
||||
res.push(`高度:${props.data.insert.image.height}px`);
|
||||
if (props.data.insert.image.size) {
|
||||
const size = bytesToSize(Number(props.data.insert.image.size));
|
||||
res.push(`大小:${size}`);
|
||||
}
|
||||
res.push(`格式:${getImageExt()}`);
|
||||
return res.join("\n");
|
||||
}
|
||||
return res.join("\n");
|
||||
return "";
|
||||
}
|
||||
|
||||
function getImageExt(): string {
|
||||
if (props.data.attributes) {
|
||||
if (props.data.attributes.ext) return props.data.attributes.ext;
|
||||
}
|
||||
if (typeof props.data.insert.image === "string") {
|
||||
const arr = props.data.insert.image.split(".");
|
||||
return arr[arr.length - 1];
|
||||
}
|
||||
return props.data.insert.image.format;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -5,28 +5,46 @@
|
||||
<img :src="localLink" alt="图片" @click="isOriSize = !isOriSize" />
|
||||
</div>
|
||||
<div class="tpoi-bottom">
|
||||
<div class="tpoi-info" v-if="props.image.attributes">
|
||||
<p v-if="props.image.attributes.size" class="tpoi-info-item">
|
||||
<span>大小:</span>
|
||||
<span>{{ bytesToSize(props.image.attributes.size ?? 0) }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
<span>尺寸:</span>
|
||||
<span>{{ props.image.attributes.width }}x{{ props.image.attributes.height }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
<span>格式:</span>
|
||||
<span>{{ format }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<template v-if="typeof props.image.insert.image !== 'string'">
|
||||
<div class="tpoi-info">
|
||||
<p class="tpoi-info-item">
|
||||
<span>大小:</span>
|
||||
<span>{{ bytesToSize(Number(props.image.insert.image.size) ?? 0) }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
<span>尺寸:</span>
|
||||
<span>
|
||||
{{ props.image.insert.image.width }}x{{ props.image.insert.image.height }}
|
||||
</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
<span>格式:</span>
|
||||
<span>{{ format }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="props.image.attributes">
|
||||
<div class="tpoi-info">
|
||||
<p v-if="props.image.attributes.size" class="tpoi-info-item">
|
||||
<span>大小:</span>
|
||||
<span>{{ bytesToSize(props.image.attributes.size ?? 0) }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
<span>尺寸:</span>
|
||||
<span>{{ props.image.attributes.width }}x{{ props.image.attributes.height }}</span>
|
||||
</p>
|
||||
<p class="tpoi-info-item">
|
||||
<span>格式:</span>
|
||||
<span>{{ format }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
<div class="tpoi-tools">
|
||||
<v-icon @click="setBlackBg" title="切换背景色" v-if="showOri">
|
||||
mdi-format-color-fill
|
||||
</v-icon>
|
||||
<v-icon @click="showOri = true" title="查看原图" v-else>mdi-magnify</v-icon>
|
||||
<v-icon @click="onCopy" title="复制到剪贴板" v-if="format !== 'gif'">
|
||||
mdi-content-copy
|
||||
</v-icon>
|
||||
<v-icon @click="onCopy" title="复制到剪贴板" v-if="showCopy">mdi-content-copy</v-icon>
|
||||
<v-icon @click="onDownload" title="下载到本地">mdi-download</v-icon>
|
||||
<v-icon @click="visible = false" title="关闭浮窗">mdi-close</v-icon>
|
||||
</div>
|
||||
@@ -51,14 +69,17 @@ const visible = defineModel<boolean>();
|
||||
const localLink = defineModel<string>("link");
|
||||
const showOri = defineModel<boolean>("ori");
|
||||
const bgColor = defineModel<string>("bgColor", { default: "transparent" });
|
||||
const format = defineModel<string>("format", { default: "png" });
|
||||
const bgMode = ref<number>(0); // 0: transparent, 1: black, 2: white
|
||||
const isOriSize = ref<boolean>(false);
|
||||
const buffer = shallowRef<Uint8Array | null>(null);
|
||||
const format = computed<string>(() => {
|
||||
if (props.image.attributes?.ext) return props.image.attributes.ext;
|
||||
const imageFormat = props.image.insert.image.split(".").pop();
|
||||
if (imageFormat !== undefined) return imageFormat;
|
||||
return "png";
|
||||
const oriLink = computed<string>(() => {
|
||||
const image = props.image.insert.image;
|
||||
return typeof image === "string" ? image : image.url;
|
||||
});
|
||||
const showCopy = computed<boolean>(() => {
|
||||
// 只能显示 png/jpg/jpeg/webp 格式的复制按钮
|
||||
return ["png", "jpg", "jpeg", "webp"].includes(format.value.toLowerCase());
|
||||
});
|
||||
|
||||
function setBlackBg(): void {
|
||||
@@ -93,14 +114,13 @@ async function onDownload(): Promise<void> {
|
||||
showOri.value = true;
|
||||
await nextTick();
|
||||
}
|
||||
const image = props.image.insert.image;
|
||||
await showLoading.start("正在下载图片到本地", image);
|
||||
if (buffer.value === null) buffer.value = await getImageBuffer(image);
|
||||
await showLoading.start("正在下载图片到本地", oriLink.value);
|
||||
if (buffer.value === null) buffer.value = await getImageBuffer(oriLink.value);
|
||||
if (buffer.value.byteLength > 80000000) {
|
||||
showSnackbar.warn("图片过大,无法下载到本地");
|
||||
return;
|
||||
}
|
||||
let fileName = image.split("/").pop()?.split(".")[0];
|
||||
let fileName = oriLink.value.split("/").pop()?.split(".")[0];
|
||||
if (fileName === undefined) fileName = Date.now().toString();
|
||||
await saveCanvasImg(buffer.value, fileName, format.value);
|
||||
await showLoading.end();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @file store/modules/app.ts
|
||||
* @description App store module
|
||||
* @since Beta v0.8.0
|
||||
* @since Beta v0.8.3
|
||||
*/
|
||||
|
||||
import { AnnoLangEnum } from "@enum/anno.js";
|
||||
@@ -84,8 +84,10 @@ const useAppStore = defineStore(
|
||||
deviceInfo.value = getInitDeviceInfo();
|
||||
}
|
||||
|
||||
function getImageUrl(url: string): string {
|
||||
if (url.endsWith(".gif") || imageQualityPercent.value === 100) return url;
|
||||
function getImageUrl(url: string, fmt?: string): string {
|
||||
let check = false;
|
||||
if (fmt && !["jpg", "png", "webp", "gif", "jpeg"].includes(fmt.toLowerCase())) check = false;
|
||||
if (check && (url.endsWith(".gif") || imageQualityPercent.value === 100)) return url;
|
||||
return `${url}?x-oss-process=image/format,jpg/quality,Q_${imageQualityPercent.value}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ async function getRenderPost(
|
||||
jsonParse = data.post.structured_content;
|
||||
} else {
|
||||
try {
|
||||
jsonParse = await parseContent(data.post.content);
|
||||
jsonParse = await parseContent(data);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
await TGLogger.Warn(`[t-post][${postId}] ${e.name}: ${e.message}`);
|
||||
@@ -277,7 +277,8 @@ async function getRenderPost(
|
||||
return JSON.parse(jsonParse);
|
||||
}
|
||||
|
||||
async function parseContent(content: string): Promise<string> {
|
||||
async function parseContent(fullData: TGApp.BBS.Post.FullData): Promise<string> {
|
||||
const content = fullData.post.content;
|
||||
const data: TGApp.BBS.SctPost.Other = JSON.parse(content);
|
||||
const result: TGApp.BBS.SctPost.Base[] = [];
|
||||
for (const key of Object.keys(data)) {
|
||||
@@ -286,7 +287,12 @@ async function parseContent(content: string): Promise<string> {
|
||||
result.push({ insert: data.describe });
|
||||
break;
|
||||
case "imgs":
|
||||
data.imgs.forEach((item) => result.push({ insert: { image: item } }));
|
||||
for (const img of data.imgs) {
|
||||
const imgFind = fullData.image_list.find((i) => i.url === img);
|
||||
if (!imgFind) {
|
||||
result.push({ insert: { image: img } });
|
||||
} else result.push({ insert: { image: imgFind } });
|
||||
}
|
||||
break;
|
||||
case "link_card_ids":
|
||||
if (!data.link_card_ids) break;
|
||||
|
||||
Reference in New Issue
Block a user