增加图片的放大缩小移动

This commit is contained in:
kura 2025-01-03 09:33:59 +08:00
parent 5bea7531c7
commit c942b7673a
2 changed files with 203 additions and 26 deletions

View File

@ -4,7 +4,8 @@
<h3>{{ file.name }}</h3> <h3>{{ file.name }}</h3>
<div class="header-actions"> <div class="header-actions">
<button <button
v-if="file.isRemote && loadedFile.type !== ''" v-if="file.isRemote"
:disabled="!loadedFile.name"
@click="downloadFile" @click="downloadFile"
> >
{{ fileTypeCheck == "unsupported-size" ? "远程传输" : "浏览器下载" }} {{ fileTypeCheck == "unsupported-size" ? "远程传输" : "浏览器下载" }}
@ -17,9 +18,41 @@
</div> </div>
<div class="reader-content"> <div class="reader-content">
<!-- 文本预览 -->
<div v-if="fileTypeCheck === 'text' || openWithText" class="text-viewer">
<div v-if="isEditing" class="editor">
<textarea
v-model="editingContent"
:style="{ height: editorHeight + 'px' }"
@input="adjustEditorHeight"
></textarea>
</div>
<pre v-else>{{ fileContent }}</pre>
</div>
<!-- 图片预览 --> <!-- 图片预览 -->
<div v-if="fileTypeCheck === 'image'" class="image-viewer"> <div
<img :src="fileUrl" :alt="file.name" /> v-else-if="fileTypeCheck === 'image'"
class="image-viewer"
@wheel.prevent="handleWheel"
@touchstart="handleTouchStart"
@touchmove.prevent="handleTouchMove"
@touchend="handleTouchEnd"
@dblclick="resetZoom"
>
<img
:src="fileUrl"
:alt="file.name"
:style="{
transform: `scale(${scale}) translate(${translateX}px, ${translateY}px)`,
transition: isTransitioning ? 'transform 0.3s' : 'none',
cursor: scale > 1 ? 'grab' : 'default',
}"
@mousedown="startDrag"
@mousemove="onDrag"
@mouseup="stopDrag"
@mouseleave="stopDrag"
/>
</div> </div>
<!-- 视频预览 --> <!-- 视频预览 -->
@ -30,6 +63,14 @@
</video> </video>
</div> </div>
<!-- 音频预览 -->
<div v-else-if="fileTypeCheck === 'audio'" class="audio-viewer">
<audio controls>
<source :src="fileUrl" :type="mimeType" />
您的浏览器不支持音频播放
</audio>
</div>
<!-- 代码预览 --> <!-- 代码预览 -->
<div v-else-if="fileTypeCheck === 'code'" class="code-viewer"> <div v-else-if="fileTypeCheck === 'code'" class="code-viewer">
<div class="code-header"> <div class="code-header">
@ -61,30 +102,23 @@
<pre v-else><code v-html="highlightedCode"></code></pre> <pre v-else><code v-html="highlightedCode"></code></pre>
</div> </div>
<!-- 文本预览 -->
<div
v-else-if="
fileTypeCheck === 'text' || fileTypeCheck === 'unsupported-type'
"
class="text-viewer"
>
<div v-if="isEditing" class="editor">
<textarea
v-model="editingContent"
:style="{ height: editorHeight + 'px' }"
@input="adjustEditorHeight"
></textarea>
</div>
<pre v-else>{{ fileContent }}</pre>
</div>
<!-- 不支持的文件类型 --> <!-- 不支持的文件类型 -->
<div v-else class="unsupported"> <div v-else class="unsupported">
<div>
{{ {{
fileTypeCheck === "unsupported-size" fileTypeCheck === "unsupported-size"
? "文件过大,无法预览,请传输后查看" ? "文件过大,无法预览,请传输后查看"
: "不支持预览该类型的文件" : "不支持预览该类型的文件"
}} }}
</div> </div>
<div>
<Button
v-if="fileTypeCheck != 'unsupported-size'"
@click="openWithText = true"
>以文本打开</Button
>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
@ -100,6 +134,7 @@ import hljs from "highlight.js";
import "highlight.js/styles/github.css"; import "highlight.js/styles/github.css";
import { Modal } from "ant-design-vue"; import { Modal } from "ant-design-vue";
import { localCurrentFile, remoteCurrentFile } from "../utils/common"; import { localCurrentFile, remoteCurrentFile } from "../utils/common";
import { NEED_CHUNK_FILE_SIZE_PREVIEW } from "../utils/fileTransfer";
const props = defineProps<{ const props = defineProps<{
file: FileInfo; file: FileInfo;
@ -116,6 +151,15 @@ const selectedLanguage = ref("auto");
const isEditing = ref(false); const isEditing = ref(false);
const editingContent = ref(""); const editingContent = ref("");
const editorHeight = ref(300); const editorHeight = ref(300);
const openWithText = ref(false);
const scale = ref(1);
const translateX = ref(0);
const translateY = ref(0);
const isTransitioning = ref(false);
const isDragging = ref(false);
const lastMouseX = ref(0);
const lastMouseY = ref(0);
const lastTouchDistance = ref(0);
// //
const downloadFile = async () => { const downloadFile = async () => {
@ -158,7 +202,7 @@ const canEdit = computed(() => {
// //
const fileTypeCheck = computed(() => { const fileTypeCheck = computed(() => {
//30M //30M
if (props.file.size > 30 * 1024 * 1024) { if (props.file.size > NEED_CHUNK_FILE_SIZE_PREVIEW) {
return "unsupported-size"; return "unsupported-size";
} }
const ext = props.file.name.split(".").pop()?.toLowerCase() || ""; const ext = props.file.name.split(".").pop()?.toLowerCase() || "";
@ -172,6 +216,10 @@ const fileTypeCheck = computed(() => {
if (["mp4", "webm", "ogg"].includes(ext)) { if (["mp4", "webm", "ogg"].includes(ext)) {
return "video"; return "video";
} }
//
if (["mp3", "wav", "ogg"].includes(ext)) {
return "audio";
}
// //
if ( if (
@ -193,6 +241,31 @@ const fileTypeCheck = computed(() => {
"lua", "lua",
"sql", "sql",
"shell", "shell",
"cfg",
"ini",
"conf",
"xml",
"yaml",
"toml",
"applescript",
"sh",
"bat",
"ps1",
"asp",
"aspx",
"php",
"jsp",
"cc",
"c++",
"cs",
"dart",
"sh",
"bash",
"kt",
"swift",
"rb",
"R",
"rust",
].includes(ext) ].includes(ext)
) { ) {
return "code"; return "code";
@ -277,6 +350,9 @@ const loadedFile = ref<{
// //
const loadFile = async () => { const loadFile = async () => {
try { try {
if (fileTypeCheck.value === "unsupported-size") {
return;
}
const file: FileData = (await props.file.getFile(true)) as FileData; const file: FileData = (await props.file.getFile(true)) as FileData;
loadedFile.value = file; loadedFile.value = file;
if (file.buffer == null) { if (file.buffer == null) {
@ -298,7 +374,98 @@ const loadFile = async () => {
} }
}; };
//
const handleWheel = (e: WheelEvent) => {
const delta = e.deltaY > 0 ? -0.1 : 0.1;
const newScale = Math.max(0.1, Math.min(5, scale.value + delta));
scale.value = newScale;
};
//
const handleTouchStart = (e: TouchEvent) => {
if (e.touches.length === 2) {
const touch1 = e.touches[0];
const touch2 = e.touches[1];
lastTouchDistance.value = Math.hypot(
touch2.clientX - touch1.clientX,
touch2.clientY - touch1.clientY
);
} else if (e.touches.length === 1) {
isDragging.value = true;
lastMouseX.value = e.touches[0].clientX;
lastMouseY.value = e.touches[0].clientY;
}
};
//
const handleTouchMove = (e: TouchEvent) => {
if (e.touches.length === 2) {
const touch1 = e.touches[0];
const touch2 = e.touches[1];
const distance = Math.hypot(
touch2.clientX - touch1.clientX,
touch2.clientY - touch1.clientY
);
const delta = distance - lastTouchDistance.value;
const scaleDelta = delta * 0.01;
scale.value = Math.max(0.1, Math.min(5, scale.value + scaleDelta));
lastTouchDistance.value = distance;
} else if (e.touches.length === 1 && isDragging.value) {
const deltaX = e.touches[0].clientX - lastMouseX.value;
const deltaY = e.touches[0].clientY - lastMouseY.value;
translateX.value += deltaX;
translateY.value += deltaY;
lastMouseX.value = e.touches[0].clientX;
lastMouseY.value = e.touches[0].clientY;
}
};
//
const handleTouchEnd = () => {
isDragging.value = false;
lastTouchDistance.value = 0;
};
//
const startDrag = (e: MouseEvent) => {
if (scale.value > 1) {
isDragging.value = true;
lastMouseX.value = e.clientX;
lastMouseY.value = e.clientY;
}
};
//
const onDrag = (e: MouseEvent) => {
if (isDragging.value) {
const deltaX = e.clientX - lastMouseX.value;
const deltaY = e.clientY - lastMouseY.value;
translateX.value += deltaX;
translateY.value += deltaY;
lastMouseX.value = e.clientX;
lastMouseY.value = e.clientY;
}
};
//
const stopDrag = () => {
isDragging.value = false;
};
//
const resetZoom = () => {
isTransitioning.value = true;
scale.value = 1;
translateX.value = 0;
translateY.value = 0;
setTimeout(() => {
isTransitioning.value = false;
}, 300);
};
onMounted(() => { onMounted(() => {
openWithText.value = false;
loadFile(); loadFile();
}); });
@ -344,12 +511,17 @@ onUnmounted(() => {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100%; height: 100%;
overflow: hidden;
user-select: none;
touch-action: none;
} }
.image-viewer img { .image-viewer img {
max-width: 100%; max-width: 100%;
max-height: 100%; max-height: 100%;
object-fit: contain; object-fit: contain;
will-change: transform;
transform-origin: center center;
} }
.video-viewer { .video-viewer {
@ -382,6 +554,7 @@ onUnmounted(() => {
.unsupported { .unsupported {
display: flex; display: flex;
flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100%; height: 100%;

View File

@ -160,6 +160,10 @@ class Peer extends EventTarget {
async handleMessage(data: Message, conn: DataConnection) { async handleMessage(data: Message, conn: DataConnection) {
const remoteD = data.data; const remoteD = data.data;
let file: FileInfo | null = null; let file: FileInfo | null = null;
if (data.type == MessageType.error) {
console.error('handleMessage recive error', data.data)
return
}
let resData: Message = { let resData: Message = {
type: MessageType.error, type: MessageType.error,
data: null, data: null,
@ -190,7 +194,7 @@ class Peer extends EventTarget {
resData.data = await navigator.clipboard.readText().then(text => { resData.data = await navigator.clipboard.readText().then(text => {
return text return text
}).catch(err => { }).catch(err => {
return '对方窗口未聚焦,无法复制' return '没有粘贴板权限或窗口未聚焦,无法复制'
}); });
break; break;
case MessageType.request_fileInfo: case MessageType.request_fileInfo: