增加图片的放大缩小移动
This commit is contained in:
parent
5bea7531c7
commit
c942b7673a
@ -4,7 +4,8 @@
|
||||
<h3>{{ file.name }}</h3>
|
||||
<div class="header-actions">
|
||||
<button
|
||||
v-if="file.isRemote && loadedFile.type !== ''"
|
||||
v-if="file.isRemote"
|
||||
:disabled="!loadedFile.name"
|
||||
@click="downloadFile"
|
||||
>
|
||||
{{ fileTypeCheck == "unsupported-size" ? "远程传输" : "浏览器下载" }}
|
||||
@ -17,9 +18,41 @@
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<img :src="fileUrl" :alt="file.name" />
|
||||
<div
|
||||
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>
|
||||
|
||||
<!-- 视频预览 -->
|
||||
@ -30,6 +63,14 @@
|
||||
</video>
|
||||
</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 class="code-header">
|
||||
@ -61,30 +102,23 @@
|
||||
<pre v-else><code v-html="highlightedCode"></code></pre>
|
||||
</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>
|
||||
{{
|
||||
fileTypeCheck === "unsupported-size"
|
||||
? "文件过大,无法预览,请传输后查看"
|
||||
: "不支持预览该类型的文件"
|
||||
}}
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
v-if="fileTypeCheck != 'unsupported-size'"
|
||||
@click="openWithText = true"
|
||||
>以文本打开</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -100,6 +134,7 @@ import hljs from "highlight.js";
|
||||
import "highlight.js/styles/github.css";
|
||||
import { Modal } from "ant-design-vue";
|
||||
import { localCurrentFile, remoteCurrentFile } from "../utils/common";
|
||||
import { NEED_CHUNK_FILE_SIZE_PREVIEW } from "../utils/fileTransfer";
|
||||
|
||||
const props = defineProps<{
|
||||
file: FileInfo;
|
||||
@ -116,6 +151,15 @@ const selectedLanguage = ref("auto");
|
||||
const isEditing = ref(false);
|
||||
const editingContent = ref("");
|
||||
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 () => {
|
||||
@ -158,7 +202,7 @@ const canEdit = computed(() => {
|
||||
// 文件类型判断
|
||||
const fileTypeCheck = computed(() => {
|
||||
//如果文件大于30M,则不预览
|
||||
if (props.file.size > 30 * 1024 * 1024) {
|
||||
if (props.file.size > NEED_CHUNK_FILE_SIZE_PREVIEW) {
|
||||
return "unsupported-size";
|
||||
}
|
||||
const ext = props.file.name.split(".").pop()?.toLowerCase() || "";
|
||||
@ -172,6 +216,10 @@ const fileTypeCheck = computed(() => {
|
||||
if (["mp4", "webm", "ogg"].includes(ext)) {
|
||||
return "video";
|
||||
}
|
||||
// 音频类型
|
||||
if (["mp3", "wav", "ogg"].includes(ext)) {
|
||||
return "audio";
|
||||
}
|
||||
|
||||
// 代码类型
|
||||
if (
|
||||
@ -193,6 +241,31 @@ const fileTypeCheck = computed(() => {
|
||||
"lua",
|
||||
"sql",
|
||||
"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)
|
||||
) {
|
||||
return "code";
|
||||
@ -277,6 +350,9 @@ const loadedFile = ref<{
|
||||
// 加载文件内容
|
||||
const loadFile = async () => {
|
||||
try {
|
||||
if (fileTypeCheck.value === "unsupported-size") {
|
||||
return;
|
||||
}
|
||||
const file: FileData = (await props.file.getFile(true)) as FileData;
|
||||
loadedFile.value = file;
|
||||
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(() => {
|
||||
openWithText.value = false;
|
||||
loadFile();
|
||||
});
|
||||
|
||||
@ -344,12 +511,17 @@ onUnmounted(() => {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.image-viewer img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
will-change: transform;
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.video-viewer {
|
||||
@ -382,6 +554,7 @@ onUnmounted(() => {
|
||||
|
||||
.unsupported {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
|
@ -160,6 +160,10 @@ class Peer extends EventTarget {
|
||||
async handleMessage(data: Message, conn: DataConnection) {
|
||||
const remoteD = data.data;
|
||||
let file: FileInfo | null = null;
|
||||
if (data.type == MessageType.error) {
|
||||
console.error('handleMessage recive error', data.data)
|
||||
return
|
||||
}
|
||||
let resData: Message = {
|
||||
type: MessageType.error,
|
||||
data: null,
|
||||
@ -190,7 +194,7 @@ class Peer extends EventTarget {
|
||||
resData.data = await navigator.clipboard.readText().then(text => {
|
||||
return text
|
||||
}).catch(err => {
|
||||
return '对方窗口未聚焦,无法复制'
|
||||
return '没有粘贴板权限或窗口未聚焦,无法复制'
|
||||
});
|
||||
break;
|
||||
case MessageType.request_fileInfo:
|
||||
|
Loading…
Reference in New Issue
Block a user