320 lines
12 KiB
Vue
320 lines
12 KiB
Vue
<template>
|
|
<!-- <div style="border: 1px solid rgb(239, 239, 245);border-radius: 5px;position: relative;"> -->
|
|
<n-scrollbar class="scrollbar">
|
|
<n-image-group>
|
|
<div v-for="msg of props.messages">
|
|
<div class="message-title">{{ `${msg.from.split('-')[0]} ${msg.timestampString}` }}</div>
|
|
<div class="message-text" v-if="msg.type1 === 'text'">
|
|
<span>{{ msg.msg }}</span>
|
|
<n-icon :component="CopyOutline" class="message-text-copy" @click='_toClipboard(msg.msg)' />
|
|
</div>
|
|
<div v-if="msg.type1 === 'img'">
|
|
<span>
|
|
<n-image :src="imgUrlCache[msg.msg]" width="100" style="margin-top: 5px;border-radius: 5px;" />
|
|
<n-skeleton v-show="!imgUrlCache[msg.msg]" height="80px" width="100px" :sharp="false"
|
|
style="border-radius: 5px;" />
|
|
</span>
|
|
</div>
|
|
|
|
<div v-if="msg.type1 === 'file'" class="message-file">
|
|
<n-flex class="message-file-content" @click="downloadFile(msg.msg)">
|
|
<n-icon :component="DownloadIcon" size="50" style="margin-top: 0px;"></n-icon>
|
|
<n-flex vertical v-if="fileInfoCache[msg.msg]">
|
|
<div style="font-weight: 500;margin-top: 8px;font-size: large;">
|
|
{{ fileInfoCache[msg.msg].fileName ? fileInfoCache[msg.msg].fileName : 'Unknow' }}
|
|
</div>
|
|
<n-flex style="margin-bottom: 8px;" justify="space-between">
|
|
<div>
|
|
{{ fileInfoCache[msg.msg].fileSize ? fileInfoCache[msg.msg].fileSize : 'Unknow'
|
|
}}
|
|
</div>
|
|
<!-- lets show u a bullshit -->
|
|
<div>
|
|
{{ fileStatus2String[fileInfoCache[msg.msg].status ? fileInfoCache[msg.msg].status :
|
|
'Unknow'] ? fileStatus2String[fileInfoCache[msg.msg].status ?
|
|
fileInfoCache[msg.msg].status : 'Unknow'] : fileInfoCache[msg.msg].status ?
|
|
fileInfoCache[msg.msg].status : 'Unknow' }}
|
|
</div>
|
|
</n-flex>
|
|
</n-flex>
|
|
</n-flex>
|
|
<!-- {{ fileInfoCache[msg.msg] }} -->
|
|
</div>
|
|
</div>
|
|
<n-empty description="无可显示消息" v-show="props.messages.length === 0" style="margin-top: 25vh;">
|
|
<template #extra>
|
|
</template>
|
|
</n-empty>
|
|
<div style="height: 30px;" v-show="props.messages.length !== 0"></div>
|
|
</n-image-group>
|
|
</n-scrollbar>
|
|
<!-- </div> -->
|
|
</template>
|
|
|
|
<script setup>
|
|
import { NScrollbar, NEmpty, NImage, NImageGroup, NSkeleton, NIcon, NFlex } from 'naive-ui'
|
|
import { ref, watch } from 'vue';
|
|
import { useSettingStore } from "@/pinia/store";
|
|
import { decrypt, decryptString, downloadFromUint8Array } from '../func/download'
|
|
import { CubeOutline as DownloadIcon, CopyOutline } from '@vicons/ionicons5'
|
|
import fileSize2String from '@/func/fileSize2String'
|
|
import useClipboard from "vue-clipboard3";
|
|
import { useMessage } from "naive-ui";
|
|
|
|
const message = useMessage()
|
|
const { toClipboard } = useClipboard();
|
|
|
|
const _toClipboard = async (text) => {
|
|
await toClipboard(text)
|
|
message.info(`已复制 ${text.length < 10 ? text : text.substring(0,7) + '...'}`)
|
|
}
|
|
const setting = useSettingStore();
|
|
const serverPort = setting.serverPort;
|
|
const baseUrl = location.protocol + "//" + location.hostname + ":" + serverPort + "/";
|
|
const props = defineProps({
|
|
messages: {
|
|
type: Array,
|
|
required: true
|
|
},
|
|
})
|
|
async function getFileInfo(code) {
|
|
let id = code.substring(0, 8);
|
|
const params = {
|
|
fileName: id
|
|
};
|
|
const options = {
|
|
method: "GET",
|
|
headers: {
|
|
'Authorization': `Bearer ${setting.token}`
|
|
// 'Content-Type':'application/json'
|
|
},
|
|
};
|
|
let response = await fetch(baseUrl + "file/info?" + new URLSearchParams(params), options)
|
|
let info = await response.json()
|
|
return info
|
|
}
|
|
|
|
async function downloadFile(code) {
|
|
if (fileInfoCache.value[code].status === 'waittingDownload') {
|
|
fileInfoCache.value[code].status = `downloading`;
|
|
return new Promise((resolve, reject) => {
|
|
let id = code.substring(0, 8);
|
|
let key = code.substring(8);
|
|
const params = {
|
|
fileName: id
|
|
};
|
|
const options = {
|
|
method: "GET",
|
|
headers: {
|
|
'Authorization': `Bearer ${setting.token}`
|
|
},
|
|
};
|
|
fetch(baseUrl + "file/download?" + new URLSearchParams(params), options)
|
|
.then(res => {
|
|
// fileName = res.headers.get("File-Name");
|
|
// fileName = decryptString(key, fileName);
|
|
// updateDownloadQuene(code, "fileName", fileName);
|
|
const total = +res.headers.get("Content-Length");
|
|
let loaded = 0;
|
|
|
|
const clone = res.clone();
|
|
const reader = res.body.getReader();
|
|
// 感觉怪怪的
|
|
const read = async () => {
|
|
const { done, value } = await reader.read();
|
|
if (done) {
|
|
// console.log(clone)
|
|
return clone;
|
|
}
|
|
loaded += value.length;
|
|
const percent = (loaded / total) * 100;
|
|
fileInfoCache.value[code].status = `下载进度:${percent.toFixed(2)}%`;
|
|
return read();
|
|
};
|
|
return read();
|
|
})
|
|
.then(res => {
|
|
var data = res.blob();
|
|
return data;
|
|
})
|
|
.then(data => {
|
|
decrypt(key, data).then(data => {
|
|
fileInfoCache.value[code].status = `finish`;
|
|
fileInfoCache.value[code].data = data
|
|
downloadFromUint8Array(data, fileInfoCache.value[code].fileName)
|
|
resolve(data)
|
|
});
|
|
})
|
|
.catch(err => {
|
|
fileInfoCache.value[code].status = `error`;
|
|
console.error(err);
|
|
});
|
|
})
|
|
} else if (fileInfoCache.value[code].status === 'finish') {
|
|
downloadFromUint8Array(fileInfoCache.value[code].data, fileInfoCache.value[code].fileName)
|
|
}
|
|
|
|
};
|
|
async function downloadImage(code) {
|
|
return new Promise((resolve, reject) => {
|
|
var id = code.substring(0, 8);
|
|
var key = code.substring(8);
|
|
var fileName;
|
|
const params = {
|
|
fileName: id
|
|
};
|
|
const options = {
|
|
method: "GET",
|
|
headers: {
|
|
'Authorization': `Bearer ${setting.token}`
|
|
// 'Content-Type':'application/json'
|
|
},
|
|
};
|
|
fetch(baseUrl + "file/download?" + new URLSearchParams(params), options)
|
|
.then(res => {
|
|
// fileName = res.headers.get("File-Name");
|
|
// fileName = decryptString(key, fileName);
|
|
// updateDownloadQuene(code, "fileName", fileName);
|
|
const total = +res.headers.get("Content-Length");
|
|
let loaded = 0;
|
|
|
|
const clone = res.clone();
|
|
const reader = res.body.getReader();
|
|
// 感觉怪怪的
|
|
const read = async () => {
|
|
const { done, value } = await reader.read();
|
|
if (done) {
|
|
// console.log(clone)
|
|
return clone;
|
|
}
|
|
loaded += value.length;
|
|
// const percent = (loaded / total) * 100;
|
|
// downloadProcess['downloadProcess'] = `下载进度:${percent.toFixed(2)}%`;
|
|
// updateDownloadQuene(
|
|
// code,
|
|
// "downloadProcess",
|
|
// `下载进度:${percent.toFixed(2)}%`
|
|
// );
|
|
// downloadProcess.value = percent.toFixed(2);
|
|
return read();
|
|
};
|
|
return read();
|
|
})
|
|
.then(res => {
|
|
var data = res.blob();
|
|
return data;
|
|
})
|
|
.then(data => {
|
|
// downloadProcess['downloadProcess'] = '解密中'
|
|
// updateDownloadQuene(code, "downloadProcess", `解密中`);
|
|
decrypt(key, data).then(data => {
|
|
// updateDownloadQuene(code, "downloadProcess", `已完成`);
|
|
// const url = URL.createObjectURL(new Blob(data))
|
|
resolve(data)
|
|
});
|
|
})
|
|
.catch(err => {
|
|
// updateDownloadQuene(
|
|
// code,
|
|
// "fileName",
|
|
// `出错,可能是文件过期或下载次数用完`
|
|
// );
|
|
// updateDownloadQuene(code, "downloadProcess", `出错`);
|
|
console.error(err);
|
|
});
|
|
})
|
|
};
|
|
const imgUrlCache = ref({})
|
|
const fileInfoCache = ref({})
|
|
const fileStatus2String = ref({
|
|
waittingDownload: '点击下载',
|
|
downloading: '下载中',
|
|
finish: '下载完成',
|
|
Unknow: '获取文件信息中',
|
|
error: '出错'
|
|
})
|
|
const imgDownloadingQueue = []
|
|
const getFileInfoQueue = []
|
|
watch(() => props.messages, async () => {
|
|
for (let msg of props.messages) {
|
|
if (msg.type1 === 'img') {
|
|
if (!imgDownloadingQueue.includes(msg.msg)) {
|
|
imgDownloadingQueue.push(msg.msg)
|
|
const imgData = await downloadImage(msg.msg)
|
|
const url = URL.createObjectURL(new Blob([imgData], { type: "image/png" }))
|
|
imgUrlCache.value[msg.msg] = url
|
|
}
|
|
} else if (msg.type1 === 'file') {
|
|
if (!getFileInfoQueue.includes(msg.msg)) {
|
|
getFileInfoQueue.push(msg.msg)
|
|
let key = msg.msg.substring(8);
|
|
const info = await getFileInfo(msg.msg)
|
|
info.fileName = decryptString(key, info.fileName)
|
|
info.fileSize = fileSize2String(info.fileSize)
|
|
info.status = 'waittingDownload'
|
|
fileInfoCache.value[msg.msg] = info
|
|
console.log(fileInfoCache.value)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.scrollbar {
|
|
padding: 10px;
|
|
padding-right: 15px;
|
|
border: 1px solid rgb(239, 239, 245);
|
|
border-radius: 5px;
|
|
position: relative;
|
|
|
|
.message-title {
|
|
color: slategrey;
|
|
}
|
|
|
|
.message-text {
|
|
position: relative;
|
|
background-color: rgb(239, 239, 245);
|
|
padding: 2px 7px;
|
|
// padding-right: 5px;
|
|
width: fit-content;
|
|
max-width: calc(100% - 28px);
|
|
border-radius: 5px;
|
|
white-space: pre-wrap;
|
|
.message-text-copy {
|
|
cursor: pointer;
|
|
position: absolute;
|
|
bottom: 8px;
|
|
right: -10px;
|
|
height: 7px;
|
|
width: 7px;
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
.message-text:hover .message-text-copy {
|
|
opacity: 1;
|
|
transition: opacity 0.3s ease-in-out;
|
|
}
|
|
|
|
.message-text:not(:hover) .message-text-copy {
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease-in-out;
|
|
transition-delay: 0.5s;
|
|
}
|
|
|
|
.message-file {
|
|
background-color: rgb(239, 239, 245);
|
|
padding: 2px 7px;
|
|
width: fit-content;
|
|
border-radius: 5px;
|
|
|
|
.message-file-content {
|
|
justify-content: center;
|
|
align-items: center;
|
|
user-select: none;
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
}
|
|
</style> |