chat中添加上传文件功能
This commit is contained in:
1 parent
709cac04f6
commit
dd56403d93
7 files changed
+420
-36
No files matched your search
@@ -53,6 +53,7 @@ CREATE TABLE IF NOT EXISTS USERS(
|
|||||||
);`);
|
);`);
|
||||||
const stmt_getPermissionGroup = db.prepare("SELECT user_group FROM USERS WHERE user_id = ? AND password_hash = ?");
|
const stmt_getPermissionGroup = db.prepare("SELECT user_group FROM USERS WHERE user_id = ? AND password_hash = ?");
|
||||||
const stmt_getFileName = db.prepare(`SELECT fileName FROM id2fileName WHERE id = ?`);
|
const stmt_getFileName = db.prepare(`SELECT fileName FROM id2fileName WHERE id = ?`);
|
||||||
|
const stmt_getFileInfo = db.prepare(`SELECT fileName,fileSize FROM id2fileName WHERE id = ?`);
|
||||||
const stmt_getUserUploadFilesSizeCount = db.prepare("SELECT uploadFilesSizeCount FROM USERS WHERE user_id = ?");
|
const stmt_getUserUploadFilesSizeCount = db.prepare("SELECT uploadFilesSizeCount FROM USERS WHERE user_id = ?");
|
||||||
const stmt_getUserGroup = db.prepare("SELECT user_group FROM USERS WHERE user_id = ?");
|
const stmt_getUserGroup = db.prepare("SELECT user_group FROM USERS WHERE user_id = ?");
|
||||||
const stmt_getTimestamp = db.prepare(`SELECT timestamp FROM id2fileName WHERE id = ?`);
|
const stmt_getTimestamp = db.prepare(`SELECT timestamp FROM id2fileName WHERE id = ?`);
|
||||||
@@ -78,6 +79,30 @@ const db_getFileName = id => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// const db_getFileInfo = id => {
|
||||||
|
// try {
|
||||||
|
// if (stmt_getFreeze.get(id)["freeze"] == 1) return null;
|
||||||
|
// return stmt_getFileSize.get(id);
|
||||||
|
// } catch (e) {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
const db_getFileInfo = (function(){
|
||||||
|
const infoCache = {}
|
||||||
|
return function(id){
|
||||||
|
if(infoCache[id]){
|
||||||
|
return infoCache[id]
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (stmt_getFreeze.get(id)["freeze"] == 1) return null;
|
||||||
|
const info = stmt_getFileInfo.get(id);
|
||||||
|
infoCache[id]=info
|
||||||
|
return info;
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
// 检查是否可下载
|
// 检查是否可下载
|
||||||
const db_downloadable = id => {
|
const db_downloadable = id => {
|
||||||
try {
|
try {
|
||||||
@@ -237,6 +262,28 @@ function downloadFile(req, res, fileId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFileInfo(req, res, fileId) {
|
||||||
|
try {
|
||||||
|
if (db_downloadable(fileId) == false) {
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// db_updateDownloadCredit(fileId);
|
||||||
|
let info = db_getFileInfo(fileId);
|
||||||
|
if (info == null) {
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.end(JSON.stringify(info));
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const getUserGroup = (function(){
|
const getUserGroup = (function(){
|
||||||
const cache = {}
|
const cache = {}
|
||||||
return function(userId){
|
return function(userId){
|
||||||
@@ -318,6 +365,20 @@ app.get("/file/download", verifyToken, function (req, res) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get("/file/info", verifyToken, function (req, res) {
|
||||||
|
res.header("Access-Control-Allow-Origin", "*");
|
||||||
|
res.header("Access-Control-Allow-Headers", "*");
|
||||||
|
res.header("Access-Control-Allow-Methods", "GET");
|
||||||
|
res.header("Access-Control-Expose-Headers", "File-Name");
|
||||||
|
var fileId = req.query.fileName;
|
||||||
|
if (!fileId) {
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end("FileName Not Found");
|
||||||
|
} else {
|
||||||
|
getFileInfo(req, res, fileId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// // 启动服务器
|
// // 启动服务器
|
||||||
// app.listen(PORT, HOST, function(){
|
// app.listen(PORT, HOST, function(){
|
||||||
// console.log(app)
|
// console.log(app)
|
||||||
@@ -442,10 +503,10 @@ function initMsgHistory(gID) {
|
|||||||
let verify = stmt_getGroupVerify.get(gID).verify;
|
let verify = stmt_getGroupVerify.get(gID).verify;
|
||||||
// 暂时这么写
|
// 暂时这么写
|
||||||
let msgHistory = db
|
let msgHistory = db
|
||||||
.prepare(`SELECT message,type,sender,timestamp FROM ${gID}`)
|
.prepare(`SELECT message,type,type1,sender,timestamp FROM ${gID}`)
|
||||||
.all()
|
.all()
|
||||||
.map(v => {
|
.map(v => {
|
||||||
return { type: v.type, msg: v.message, from: v.sender, timestamp: v.timestamp };
|
return { type: v.type,type1:v.type1, msg: v.message, from: v.sender, timestamp: v.timestamp };
|
||||||
});
|
});
|
||||||
// console.log(msgHistory)
|
// console.log(msgHistory)
|
||||||
groupsMap[gID] = { verify, member: [], msgHistory };
|
groupsMap[gID] = { verify, member: [], msgHistory };
|
||||||
|
|||||||
+5
-4
@@ -10,11 +10,12 @@ import { NNotificationProvider, NMessageProvider, NCard, NMenu, NIcon,NModalProv
|
|||||||
import { ref, watch, h ,shallowRef, onMounted, computed} from "vue";
|
import { ref, watch, h ,shallowRef, onMounted, computed} from "vue";
|
||||||
// import { useRoute } from "vue-router";
|
// import { useRoute } from "vue-router";
|
||||||
import {
|
import {
|
||||||
ArrowUpCircleOutline as uploadIcon,
|
CloudUploadOutline as uploadIcon,
|
||||||
ArrowDownCircleOutline as downloadIcon,
|
CloudDownloadOutline as downloadIcon,
|
||||||
ChatboxEllipsesOutline as chatIcon,
|
// ChatboxEllipsesOutline as chatIcon,
|
||||||
|
ChatbubblesOutline as chatIcon,
|
||||||
SettingsOutline as settingIcon,
|
SettingsOutline as settingIcon,
|
||||||
PersonOutline as AccountIcon
|
FootstepsOutline as AccountIcon
|
||||||
|
|
||||||
} from '@vicons/ionicons5'
|
} from '@vicons/ionicons5'
|
||||||
import { useSettingStore } from "@/pinia/store";
|
import { useSettingStore } from "@/pinia/store";
|
||||||
|
|||||||
+239
-6
@@ -1,29 +1,262 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- <div style="border: 1px solid rgb(239, 239, 245);border-radius: 5px;position: relative;"> -->
|
<!-- <div style="border: 1px solid rgb(239, 239, 245);border-radius: 5px;position: relative;"> -->
|
||||||
<n-scrollbar style="padding: 10px;padding-right: 15px; border: 1px solid rgb(239, 239, 245);border-radius: 5px;position: relative;">
|
<n-scrollbar
|
||||||
|
style="padding: 10px;padding-right: 15px; border: 1px solid rgb(239, 239, 245);border-radius: 5px;position: relative;">
|
||||||
|
<n-image-group>
|
||||||
|
|
||||||
<div v-for="msg of props.messages">
|
<div v-for="msg of props.messages">
|
||||||
<div style="color:slategrey;">{{ `${msg.from.split('-')[0]} ${msg.timestampString}` }}</div>
|
<div style="color:slategrey;">{{ `${msg.from.split('-')[0]} ${msg.timestampString}` }}</div>
|
||||||
<div
|
<div style="background-color: rgb(239, 239, 245); padding: 2px 7px; width: fit-content; border-radius: 5px; white-space: pre-wrap;"
|
||||||
style="background-color: rgb(239, 239, 245); padding: 2px 7px; width: fit-content; border-radius: 5px; white-space: pre-wrap;">
|
v-if="msg.type1 === 'text'">
|
||||||
{{ msg.msg }}</div>
|
<span>{{ msg.msg }}</span>
|
||||||
|
<!-- <span v-if="msg.type1 === 'img'"> -->
|
||||||
|
<!-- <n-image :src="imgUrlCache[msg.msg]" width="100" style="margin-top: 5px;border-radius: 5px;" /> -->
|
||||||
|
<!-- img->>{{ msg.msg }} -->
|
||||||
|
<!-- </span> -->
|
||||||
|
</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'"
|
||||||
|
style="background-color: rgb(239, 239, 245); padding: 2px 7px; width: fit-content; border-radius: 5px;">
|
||||||
|
<n-flex style="justify-content: center;align-items: center;user-select: none;cursor: pointer;"
|
||||||
|
@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>
|
</div>
|
||||||
<n-empty description="无可显示消息" v-show="props.messages.length === 0" style="margin-top: 25vh;">
|
<n-empty description="无可显示消息" v-show="props.messages.length === 0" style="margin-top: 25vh;">
|
||||||
<template #extra>
|
<template #extra>
|
||||||
</template>
|
</template>
|
||||||
</n-empty>
|
</n-empty>
|
||||||
<div style="height: 30px;" v-show="props.messages.length !== 0"></div>
|
<div style="height: 30px;" v-show="props.messages.length !== 0"></div>
|
||||||
|
</n-image-group>
|
||||||
</n-scrollbar>
|
</n-scrollbar>
|
||||||
<!-- </div> -->
|
<!-- </div> -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { NScrollbar,NEmpty } from 'naive-ui'
|
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 } from '@vicons/ionicons5'
|
||||||
|
import fileSize2String from '@/func/fileSize2String'
|
||||||
|
|
||||||
|
const setting = useSettingStore();
|
||||||
|
const serverPort = setting.serverPort;
|
||||||
|
const baseUrl = location.protocol + "//" + location.hostname + ":" + serverPort + "/";
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
messages: {
|
messages: {
|
||||||
type: Number,
|
type: Array,
|
||||||
required: true
|
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') {
|
||||||
|
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]))
|
||||||
|
imgUrlCache.value[msg.msg] = url
|
||||||
|
// console.log(new Blob(imgData), imgData, url, imgUrlCache.value, imgDownloadingQueue)
|
||||||
|
}
|
||||||
|
} 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>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style></style>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export default function filePicker({ fn, accept, multiple } = {}) {
|
||||||
|
const inpEle = document.createElement("input");
|
||||||
|
inpEle.id = `__file_${parseInt(Math.random() * 100000000)}`;
|
||||||
|
inpEle.type = "file";
|
||||||
|
inpEle.style.display = "none";
|
||||||
|
// 文件类型限制
|
||||||
|
// inpEle.accept="image/*"
|
||||||
|
accept && (inpEle.accept = accept);
|
||||||
|
// 多选限制
|
||||||
|
multiple && (inpEle.multiple = multiple);
|
||||||
|
// inpEle.addEventListener("change", event => fn.call(inpEle, event, inpEle.files), { once: true });
|
||||||
|
inpEle.addEventListener("change", e => fn(inpEle.files), { once: true });
|
||||||
|
inpEle.click();
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
function pow1024(num) {
|
||||||
|
return Math.pow(1024, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function(size){
|
||||||
|
if (!size) return '';
|
||||||
|
return size < 1024 ? size + ' B' :
|
||||||
|
size < pow1024(2) ? (size / 1024).toFixed(2) + ' KB' :
|
||||||
|
size < pow1024(3) ? (size / pow1024(2)).toFixed(2) + ' MB' :
|
||||||
|
size < pow1024(4) ? (size / pow1024(3)).toFixed(2) + ' GB' :
|
||||||
|
(size / pow1024(4)).toFixed(2) + ' TB'
|
||||||
|
}
|
||||||
@@ -73,14 +73,18 @@ const encrypt = (key, data, fileName, setting, message) => {
|
|||||||
encrypted_data = data
|
encrypted_data = data
|
||||||
// AES
|
// AES
|
||||||
}else if(encryptFunction != "SM4"){
|
}else if(encryptFunction != "SM4"){
|
||||||
|
if(message)
|
||||||
message.info(`开始加密${fileName}`);
|
message.info(`开始加密${fileName}`);
|
||||||
encrypted_data = AES(key,data)
|
encrypted_data = AES(key,data)
|
||||||
|
if(message)
|
||||||
message.info(`${fileName}加密完成`);
|
message.info(`${fileName}加密完成`);
|
||||||
// SM4
|
// SM4
|
||||||
}else{
|
}else{
|
||||||
|
if(message)
|
||||||
message.info(`开始加密${fileName}`);
|
message.info(`开始加密${fileName}`);
|
||||||
key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
|
key = [...aesjs.utils.utf8.toBytes(key.substring(3))];
|
||||||
encrypted_data = SM4(key,data)
|
encrypted_data = SM4(key,data)
|
||||||
|
if(message)
|
||||||
message.info(`${fileName}加密完成`);
|
message.info(`${fileName}加密完成`);
|
||||||
}
|
}
|
||||||
return encrypted_data
|
return encrypted_data
|
||||||
|
|||||||
+83
-24
@@ -38,12 +38,14 @@
|
|||||||
<chatui style="margin-bottom: 10px; height: 65vh"
|
<chatui style="margin-bottom: 10px; height: 65vh"
|
||||||
:messages="activeGID ? Messages[activeGID].map(v => { v.timestampString = new Date(v.timestamp).toLocaleString(); return v }).reverse() : []"/>
|
:messages="activeGID ? Messages[activeGID].map(v => { v.timestampString = new Date(v.timestamp).toLocaleString(); return v }).reverse() : []"/>
|
||||||
|
|
||||||
<n-input v-model:value="msg" type="textarea" placeholder="发送信息" :autosize="{
|
<n-input v-model:value="msgInput" type="textarea" placeholder="发送信息" :autosize="{
|
||||||
minRows: 1,
|
minRows: 1,
|
||||||
maxRows: 2
|
maxRows: 2
|
||||||
}" @keydown="handleKeyDown">
|
}" @keydown="handleKeyDown" style="margin-top: 0px;">
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<n-icon :component="SendOutline" style="cursor: pointer;" @click='sendMsg(activeGID, msg)'/>
|
<n-icon :component="DocumentsOutline" style="cursor: pointer;margin-right: 5px;" @click='filePicker({fn:sendFiles,multiple:true})'/>
|
||||||
|
<n-icon :component="ImagesOutline" style="cursor: pointer;margin-right: 5px;" @click='filePicker({fn:sendImages,accept:"image/*",multiple:true})'/>
|
||||||
|
<n-icon :component="SendOutline" style="cursor: pointer;" @click='sendMsg(activeGID, msgInput)'/>
|
||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
|
|
||||||
@@ -53,6 +55,8 @@
|
|||||||
</n-button> -->
|
</n-button> -->
|
||||||
</div>
|
</div>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
<!-- {{ dataTest }} -->
|
||||||
|
<!-- {{ Messages }} -->
|
||||||
|
|
||||||
|
|
||||||
<n-modal v-model:show="showModal">
|
<n-modal v-model:show="showModal">
|
||||||
@@ -77,21 +81,27 @@ import { ref,onMounted } from 'vue'
|
|||||||
import useClipboard from "vue-clipboard3";
|
import useClipboard from "vue-clipboard3";
|
||||||
import { NIcon, NFlex, NDataTable, NButton, NInput, NSelect,NModal,NCard , NQrCode} from 'naive-ui'
|
import { NIcon, NFlex, NDataTable, NButton, NInput, NSelect,NModal,NCard , NQrCode} from 'naive-ui'
|
||||||
import { useSettingStore } from "@/pinia/store";
|
import { useSettingStore } from "@/pinia/store";
|
||||||
import { generateKey, encryptString } from '@/func/upload';
|
import { generateKey, encryptString, encrypt } from '@/func/upload';
|
||||||
import { decryptString } from '@/func/download';
|
import { decryptString } from '@/func/download';
|
||||||
import WebsocketHeartbeatJs from 'websocket-heartbeat-js';
|
import WebsocketHeartbeatJs from 'websocket-heartbeat-js';
|
||||||
import { CopyOutline,SendOutline } from '@vicons/ionicons5'
|
import { CopyOutline,SendOutline,ImagesOutline,DocumentsOutline } from '@vicons/ionicons5'
|
||||||
import getQueryString from '@/func/getQuery'
|
import getQueryString from '@/func/getQuery'
|
||||||
import chatui from "@/components/chatui.vue"
|
import chatui from "@/components/chatui.vue"
|
||||||
|
import { Base64 } from 'js-base64';
|
||||||
|
import { useMessage, useNotification } from "naive-ui";
|
||||||
|
import filePicker from "@/func/filePicker"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
// const modal = useModal();
|
// const modal = useModal();
|
||||||
const { toClipboard } = useClipboard();
|
const { toClipboard } = useClipboard();
|
||||||
|
|
||||||
const setting = useSettingStore();
|
const setting = useSettingStore();
|
||||||
const serverPort = setting.serverPort;
|
const serverPort = setting.serverPort;
|
||||||
const baseUrl = 'ws' + "://" + location.hostname + ":" + serverPort;
|
const wsBaseUrl = 'ws' + "://" + location.hostname + ":" + serverPort;
|
||||||
|
const baseUrl = location.protocol + "//" + location.hostname + ":" + serverPort + "/";
|
||||||
const QRIconUrl = ref(location.protocol + "//" + location.hostname + ":" + serverPort + "/favicon.ico")
|
const QRIconUrl = ref(location.protocol + "//" + location.hostname + ":" + serverPort + "/favicon.ico")
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@@ -160,7 +170,7 @@ const Messages = ref({})
|
|||||||
const dataTest = ref([])
|
const dataTest = ref([])
|
||||||
const gID = ref('')
|
const gID = ref('')
|
||||||
// const codeInput = ref('')
|
// const codeInput = ref('')
|
||||||
const msg = ref('')
|
const msgInput = ref('')
|
||||||
|
|
||||||
const joinedGroup = ref([])
|
const joinedGroup = ref([])
|
||||||
// 由server随机分配uuid
|
// 由server随机分配uuid
|
||||||
@@ -170,7 +180,7 @@ const uuid = ref('')
|
|||||||
const gID2key = {}
|
const gID2key = {}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
url: baseUrl,
|
url: wsBaseUrl,
|
||||||
pingTimeout: 20000,
|
pingTimeout: 20000,
|
||||||
pongTimeout: 10000,
|
pongTimeout: 10000,
|
||||||
reconnectTimeout: 3000,
|
reconnectTimeout: 3000,
|
||||||
@@ -216,7 +226,7 @@ socket.onmessage = (event) => {
|
|||||||
let key = gID2key[gID].key
|
let key = gID2key[gID].key
|
||||||
Messages.value[msg.gID].length = 0
|
Messages.value[msg.gID].length = 0
|
||||||
for (let m of JSON.parse(msg.history)) {
|
for (let m of JSON.parse(msg.history)) {
|
||||||
Messages.value[msg.gID].push({ type: m.type, msg: decryptString(key, m.msg), from: m.from, timestamp: m.timestamp })
|
Messages.value[msg.gID].push({ type: m.type,type1:m.type1, msg: decryptString(key, m.msg), from: m.from, timestamp: m.timestamp })
|
||||||
dataTest.value.push({ from: 'decrypt', msg: decryptString(key, m.msg) })
|
dataTest.value.push({ from: 'decrypt', msg: decryptString(key, m.msg) })
|
||||||
}
|
}
|
||||||
} else if (type1 === 'uuid') {
|
} else if (type1 === 'uuid') {
|
||||||
@@ -226,7 +236,6 @@ socket.onmessage = (event) => {
|
|||||||
let gID = msg.gID
|
let gID = msg.gID
|
||||||
let key = gID2key[gID].key
|
let key = gID2key[gID].key
|
||||||
Messages.value[msg.gID].push({ type: msg.type,type1:msg.type1, msg: decryptString(key, msg.msg), from: msg.from, timestamp: msg.timestamp })
|
Messages.value[msg.gID].push({ type: msg.type,type1:msg.type1, msg: decryptString(key, msg.msg), from: msg.from, timestamp: msg.timestamp })
|
||||||
// console.log(Messages.value)
|
|
||||||
dataTest.value.push({ from: 'decrypt', msg: decryptString(key, msg.msg) })
|
dataTest.value.push({ from: 'decrypt', msg: decryptString(key, msg.msg) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,25 +287,75 @@ function sendMsg(gID, msg) {
|
|||||||
let key = gID2key[gID].key
|
let key = gID2key[gID].key
|
||||||
msg = encryptString(key, msg, setting)
|
msg = encryptString(key, msg, setting)
|
||||||
socket.send(JSON.stringify({ type: 'message',type1:'text', msg, gID }))
|
socket.send(JSON.stringify({ type: 'message',type1:'text', msg, gID }))
|
||||||
|
msgInput.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendImg(gID,code){
|
// async function encryptImg(file){
|
||||||
if(msg === '' || activeGID.value === null)return
|
// var f = new Blob([file]);
|
||||||
let key = gID2key[gID].key
|
// let key = gID2key[gID].key
|
||||||
msg = encryptString(key, msg, setting)
|
// let eU8A = await encrypt(key, f, fileName, setting)
|
||||||
socket.send(JSON.stringify({ type: 'message',type1:'img', msg, gID }))
|
// let eb = Base64.fromUint8Array(eU8A)
|
||||||
|
// return eb
|
||||||
|
// }
|
||||||
|
|
||||||
|
async function uploadFile(file){
|
||||||
|
if(activeGID.value === null)return
|
||||||
|
var fileName = file.name;
|
||||||
|
let key = gID2key[activeGID.value].key
|
||||||
|
const encryptedFileName = encryptString(key, fileName, setting);
|
||||||
|
var f = new Blob([file]);
|
||||||
|
let data = await encrypt(key, f, fileName, setting)
|
||||||
|
const params = {
|
||||||
|
fileName: encryptedFileName,
|
||||||
|
downloadCredit:-1
|
||||||
|
};
|
||||||
|
message.info(`开始上传${fileName}`)
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
// onProgress({ percent: 1 });
|
||||||
|
xhr.open(
|
||||||
|
"POST",
|
||||||
|
baseUrl + "file/upload?" + new URLSearchParams(params)
|
||||||
|
);
|
||||||
|
// console.log(setting.token)
|
||||||
|
xhr.setRequestHeader('Authorization', `Bearer ${setting.token}`);
|
||||||
|
xhr.send(data);
|
||||||
|
const code = await new Promise((resolve,reject)=>{
|
||||||
|
xhr.onreadystatechange = () => {
|
||||||
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||||
|
let id = xhr.responseText;
|
||||||
|
let code = id + key
|
||||||
|
message.success(`上传${fileName}成功`)
|
||||||
|
resolve(code)
|
||||||
|
} else {
|
||||||
|
if(xhr.statusText !== 'OK'){
|
||||||
|
console.error(xhr.statusText);
|
||||||
|
message.error(`上传${fileName}失败`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
console.log(code)
|
||||||
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function sendImages(files){
|
||||||
|
if(activeGID.value === null || files.length === 0)return
|
||||||
|
for(let file of files){
|
||||||
|
const code = await uploadFile(file)
|
||||||
|
let key = gID2key[activeGID.value].key
|
||||||
|
socket.send(JSON.stringify({ type: 'message',type1:'img', msg: encryptString(key, code), gID:activeGID.value }))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 通过地址传参并加入传入的群组
|
async function sendFiles(files){
|
||||||
// function getQueryString(key) {
|
if(activeGID.value === null || files.length === 0)return
|
||||||
// var reg = new RegExp('(^|&)' + key + '=([^&]*)(&|$)', 'i');
|
for(let file of files){
|
||||||
// var r = window.location.search.substring(1).match(reg);
|
let fileName = file.name;
|
||||||
// if (r != null) {
|
const code = await uploadFile(file)
|
||||||
// return unescape(r[2]);
|
let key = gID2key[activeGID.value].key
|
||||||
// }
|
socket.send(JSON.stringify({ type: 'message',type1:'file', msg: encryptString(key, code), gID:activeGID.value }))
|
||||||
// return null;
|
};
|
||||||
// }
|
}
|
||||||
onMounted(()=>{
|
onMounted(()=>{
|
||||||
let aP = getQueryString('aP')
|
let aP = getQueryString('aP')
|
||||||
let cG = getQueryString('cG')
|
let cG = getQueryString('cG')
|
||||||
|
|||||||
Reference in new issue
Block a user