mirror of
https://github.com/BTMuli/TeyvatGuide.git
synced 2025-12-15 09:48:14 +08:00
✨ 图片浮窗
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tp-image-box">
|
<div class="tp-image-box" @click="showOverlay = true">
|
||||||
<img
|
<img
|
||||||
:style="getImageStyle()"
|
:style="getImageStyle()"
|
||||||
:src="getImageUrl()"
|
:src="getImageUrl()"
|
||||||
@@ -7,13 +7,15 @@
|
|||||||
:title="getImageTitle()"
|
:title="getImageTitle()"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<TpoImage :image="props.data" v-model="showOverlay" />
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { StyleValue, toRaw } from "vue";
|
import { StyleValue, ref } from "vue";
|
||||||
|
|
||||||
|
import TpoImage from "./tpo-image.vue";
|
||||||
import { bytesToSize } from "../../utils/toolFunc";
|
import { bytesToSize } from "../../utils/toolFunc";
|
||||||
|
|
||||||
interface TpImage {
|
export interface TpImage {
|
||||||
insert: {
|
insert: {
|
||||||
image: string;
|
image: string;
|
||||||
};
|
};
|
||||||
@@ -31,11 +33,13 @@ interface TpImageProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<TpImageProps>();
|
const props = defineProps<TpImageProps>();
|
||||||
|
const showOverlay = ref(false);
|
||||||
|
|
||||||
console.log("tp-image", props.data.insert.image, toRaw(props.data).attributes);
|
console.log("tp-image", props.data.insert.image, props.data.attributes);
|
||||||
|
|
||||||
function getImageStyle(): StyleValue {
|
function getImageStyle(): StyleValue {
|
||||||
let style: StyleValue = <Array<StyleValue>>[];
|
let style: StyleValue = <Array<StyleValue>>[];
|
||||||
|
style.push("cursor: pointer");
|
||||||
if (props.data.attributes == undefined) return style;
|
if (props.data.attributes == undefined) return style;
|
||||||
if (props.data.attributes.width < 800) {
|
if (props.data.attributes.width < 800) {
|
||||||
const widthFullRule: StyleValue = "width: 100%";
|
const widthFullRule: StyleValue = "width: 100%";
|
||||||
|
|||||||
156
src/components/post/tpo-image.vue
Normal file
156
src/components/post/tpo-image.vue
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<template>
|
||||||
|
<TOverlay v-model="visible" hide :to-click="onCancel" blur-val="10px">
|
||||||
|
<div class="tpoi-box">
|
||||||
|
<img :src="props.image.insert.image" alt="图片" />
|
||||||
|
<div class="tpoi-bottom">
|
||||||
|
<div class="tpoi-info" v-if="props.image.attributes">
|
||||||
|
<p v-if="props.image.attributes.size">
|
||||||
|
大小:{{ bytesToSize(props.image.attributes.size ?? 0) }}
|
||||||
|
</p>
|
||||||
|
<p>尺寸:{{ props.image.attributes.width }}x{{ props.image.attributes.height }}</p>
|
||||||
|
<p>格式:{{ format }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="tpoi-tools">
|
||||||
|
<v-icon @click="setBlackBg" title="切换背景色">mdi-format-color-fill</v-icon>
|
||||||
|
<v-icon @click="onCopy" title="复制到剪贴板">mdi-content-copy</v-icon>
|
||||||
|
<v-icon @click="onDownload" title="下载到本地">mdi-download</v-icon>
|
||||||
|
<v-icon @click="onCancel" title="关闭浮窗">mdi-close</v-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TOverlay>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, onUnmounted, ref } from "vue";
|
||||||
|
|
||||||
|
import { TpImage } from "./tp-image.vue";
|
||||||
|
import { copyToClipboard, getImageBuffer, saveCanvasImg } from "../../utils/TGShare";
|
||||||
|
import { bytesToSize } from "../../utils/toolFunc";
|
||||||
|
import showSnackbar from "../func/snackbar";
|
||||||
|
import TOverlay from "../main/t-overlay.vue";
|
||||||
|
|
||||||
|
interface TpoImageProps {
|
||||||
|
image: TpImage;
|
||||||
|
modelValue: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TpoImageEmits = {
|
||||||
|
(e: "update:modelValue", v: boolean): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = defineProps<TpoImageProps>();
|
||||||
|
const emits = defineEmits<TpoImageEmits>();
|
||||||
|
const buffer = ref<Uint8Array | null>(null);
|
||||||
|
const bgMode = ref(0); // 0: transparent, 1: black, 2: white
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (value) => {
|
||||||
|
emits("update:modelValue", value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const bg = computed(() => {
|
||||||
|
if (bgMode.value === 1) return "black";
|
||||||
|
if (bgMode.value === 2) return "white";
|
||||||
|
return "transparent";
|
||||||
|
});
|
||||||
|
|
||||||
|
const format = computed(() => {
|
||||||
|
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";
|
||||||
|
});
|
||||||
|
|
||||||
|
function onCancel() {
|
||||||
|
visible.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setBlackBg() {
|
||||||
|
bgMode.value = (bgMode.value + 1) % 3;
|
||||||
|
const bgLabelList = ["透明", "黑色", "白色"];
|
||||||
|
showSnackbar({
|
||||||
|
text: `背景已切换为${bgLabelList[bgMode.value]}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onCopy(): Promise<void> {
|
||||||
|
if (format.value === "gif") {
|
||||||
|
showSnackbar({
|
||||||
|
text: "GIF 图片不支持复制到剪贴板",
|
||||||
|
color: "error",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const image = props.image.insert.image;
|
||||||
|
if (buffer.value === null) buffer.value = await getImageBuffer(image);
|
||||||
|
const size = bytesToSize(buffer.value.byteLength);
|
||||||
|
await copyToClipboard(buffer.value);
|
||||||
|
showSnackbar({ text: `图片已复制到剪贴板,大小:${size}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDownload() {
|
||||||
|
const image = props.image.insert.image;
|
||||||
|
if (buffer.value === null) buffer.value = await getImageBuffer(image);
|
||||||
|
const size = bytesToSize(buffer.value.byteLength);
|
||||||
|
if (buffer.value.byteLength > 80000000) {
|
||||||
|
showSnackbar({
|
||||||
|
text: "图片过大,无法下载到本地",
|
||||||
|
color: "error",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await saveCanvasImg(buffer.value, Date.now().toString(), format.value);
|
||||||
|
showSnackbar({ text: `图片已下载到本地,大小:${size}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
buffer.value = null;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.tpoi-box {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
min-width: 400px;
|
||||||
|
max-width: 90vw;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
row-gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tpoi-box img {
|
||||||
|
max-width: 90vw;
|
||||||
|
max-height: 70vh;
|
||||||
|
background-color: v-bind(bg);
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tpoi-bottom {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tpoi-info {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: var(--common-shadow-2);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tpoi-tools {
|
||||||
|
display: flex;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: var(--common-shadow-2);
|
||||||
|
color: white;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tpoi-tools v-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* @file utils/TGShare.ts
|
* @file utils/TGShare.ts
|
||||||
* @description 生成分享截图并保存到本地
|
* @description 生成分享截图并保存到本地
|
||||||
* @since Beta v0.4.2
|
* @since Beta v0.4.4
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { dialog, fs, http, path } from "@tauri-apps/api";
|
import { dialog, fs, http, path } from "@tauri-apps/api";
|
||||||
@@ -14,17 +14,23 @@ import showSnackbar from "../components/func/snackbar";
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 保存图片-canvas
|
* @description 保存图片-canvas
|
||||||
* @since Beta v0.4.2
|
* @since Beta v0.4.4
|
||||||
* @param {Uint8Array} buffer - 图片数据
|
* @param {Uint8Array} buffer - 图片数据
|
||||||
* @param {string} filename - 文件名
|
* @param {string} filename - 文件名
|
||||||
|
* @param {string} format - 文件格式
|
||||||
* @returns {Promise<void>} 无返回值
|
* @returns {Promise<void>} 无返回值
|
||||||
*/
|
*/
|
||||||
async function saveCanvasImg(buffer: Uint8Array, filename: string): Promise<void> {
|
export async function saveCanvasImg(
|
||||||
|
buffer: Uint8Array,
|
||||||
|
filename: string,
|
||||||
|
format?: string,
|
||||||
|
): Promise<void> {
|
||||||
|
if (format === undefined) format = "png";
|
||||||
await dialog
|
await dialog
|
||||||
.save({
|
.save({
|
||||||
title: "保存图片",
|
title: "保存图片",
|
||||||
filters: [{ name: "图片", extensions: ["png"] }],
|
filters: [{ name: "图片", extensions: [format] }],
|
||||||
defaultPath: `${await path.downloadDir()}${path.sep}${filename}.png`,
|
defaultPath: `${await path.downloadDir()}${path.sep}${filename}.${format}`,
|
||||||
})
|
})
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
if (res === null) return;
|
if (res === null) return;
|
||||||
@@ -55,6 +61,21 @@ export async function saveImgLocal(url: string): Promise<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 返回图片 buffer
|
||||||
|
* @since Beta v0.4.4
|
||||||
|
* @param {string} url - 图片链接
|
||||||
|
* @returns {Promise<Uint8Array>} 图片 buffer
|
||||||
|
*/
|
||||||
|
export async function getImageBuffer(url: string): Promise<Uint8Array> {
|
||||||
|
return await http
|
||||||
|
.fetch<ArrayBuffer>(url, {
|
||||||
|
method: "GET",
|
||||||
|
responseType: http.ResponseType.Binary,
|
||||||
|
})
|
||||||
|
.then((res) => new Uint8Array(res.data));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description 获取分享截图背景色
|
* @description 获取分享截图背景色
|
||||||
* @since Beta v0.3.2
|
* @since Beta v0.3.2
|
||||||
@@ -151,7 +172,7 @@ export async function generateShareImg(
|
|||||||
* @param {Uint8Array} buffer - 图片数据
|
* @param {Uint8Array} buffer - 图片数据
|
||||||
* @returns {Promise<void>} 无返回值
|
* @returns {Promise<void>} 无返回值
|
||||||
*/
|
*/
|
||||||
async function copyToClipboard(buffer: Uint8Array): Promise<void> {
|
export async function copyToClipboard(buffer: Uint8Array): Promise<void> {
|
||||||
const blob = new Blob([buffer], { type: "image/png" });
|
const blob = new Blob([buffer], { type: "image/png" });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
await navigator.clipboard.write([
|
await navigator.clipboard.write([
|
||||||
|
|||||||
Reference in New Issue
Block a user