增加图片的放大缩小移动
This commit is contained in:
parent
5bea7531c7
commit
c942b7673a
@ -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%;
|
||||||
|
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user