init
This commit is contained in:
commit
97a4391120
20 files changed
+3709
No files matched your search
+34
@@ -0,0 +1,34 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# files created with miaoShare service
|
||||||
|
temp
|
||||||
|
miaoShare.db
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>miaoShare</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
<style>
|
||||||
|
html,body,#app{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
Generated
+2526
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "miaoshare",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"server": "concurrently \"node server.js\" \"node server_page.js\"",
|
||||||
|
"miaoShare": "concurrently \"node server.js\" \"vite\"",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"aes-js": "^3.1.2",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
|
"js-base64": "^3.7.6",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"sm-crypto": "^0.3.13",
|
||||||
|
"vicons": "^0.0.1",
|
||||||
|
"vue": "^3.4.15",
|
||||||
|
"vue-clipboard3": "^2.0.0",
|
||||||
|
"vue-router": "^4.2.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"better-sqlite3": "^8.7.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.3",
|
||||||
|
"concurrently": "^8.2.2",
|
||||||
|
"naive-ui": "^2.37.3",
|
||||||
|
"vfonts": "^0.0.3",
|
||||||
|
"vite": "^5.0.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,213 @@
|
|||||||
|
import express from 'express'
|
||||||
|
import cors from 'cors'
|
||||||
|
import Database from 'better-sqlite3';
|
||||||
|
import fs from "fs"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const HOST = '0.0.0.0'
|
||||||
|
const PORT = '17058'
|
||||||
|
const fileDir = "./temp";
|
||||||
|
const dbPath = './miaoShare.db'
|
||||||
|
// 上传文件最大大小:200MB
|
||||||
|
const uploadMaxSize=200*1024*1024
|
||||||
|
// 一天的毫秒数
|
||||||
|
const millisecondsOfTheDay = parseInt(24*3600*1000)
|
||||||
|
// 文件3天过期
|
||||||
|
const LifeOfFile=millisecondsOfTheDay*3
|
||||||
|
// const LifeOfFile=0
|
||||||
|
|
||||||
|
// 数据库相关
|
||||||
|
const db = new Database(dbPath, { verbose: null });
|
||||||
|
db.prepare(`CREATE TABLE IF NOT EXISTS id2fileName (
|
||||||
|
id CHAR(8) PRIMARY KEY NOT NULL UNIQUE,
|
||||||
|
fileName TEXT NOT NULL,
|
||||||
|
timestamp INT NOT NULL,
|
||||||
|
freeze tinyint(1) NOT NULL,
|
||||||
|
downloadCredit INT NOT NULL
|
||||||
|
);`).run()
|
||||||
|
const stmt_getFileName = db.prepare(`SELECT fileName FROM id2fileName WHERE id = ?`)
|
||||||
|
const stmt_getTimestamp = db.prepare(`SELECT timestamp FROM id2fileName WHERE id = ?`)
|
||||||
|
const stmt_getFreeze = db.prepare(`SELECT freeze FROM id2fileName WHERE id = ?`)
|
||||||
|
const stmt_getDownloadCredit = db.prepare(`SELECT downloadCredit FROM id2fileName WHERE id = ?`)
|
||||||
|
const stmt_getFreezenIDs = db.prepare(`SELECT id FROM id2fileName WHERE freeze = 1`)
|
||||||
|
const stmt_insertId2FileName = db.prepare(`INSERT INTO id2fileName (id, fileName, timestamp, freeze, downloadCredit) VALUES (?, ?, ?, ?, ?)`)
|
||||||
|
const stmt_deleteExpireData = db.prepare(`DELETE FROM id2fileName WHERE freeze=1`)
|
||||||
|
const stmt_freezeExpireData = db.prepare(`UPDATE id2fileName SET freeze=1 WHERE timestamp < ? OR downloadCredit < 1`)
|
||||||
|
const stmt_updateDownloadCredit = db.prepare(`UPDATE id2fileName SET downloadCredit=downloadCredit-1 WHERE id = ?`)
|
||||||
|
|
||||||
|
const db_insert = (id,fileName,downloadCredit)=>{
|
||||||
|
stmt_insertId2FileName.run(id,fileName,parseInt(Date.now()),0,downloadCredit)
|
||||||
|
}
|
||||||
|
const db_getFileName = (id)=>{
|
||||||
|
try{
|
||||||
|
if(stmt_getFreeze.get(id)['freeze'] == 1)return null
|
||||||
|
return stmt_getFileName.get(id)['fileName']
|
||||||
|
}catch(e){
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 检查是否可下载
|
||||||
|
const db_downloadable = (id)=>{
|
||||||
|
try{
|
||||||
|
// 懒
|
||||||
|
if(stmt_getDownloadCredit.get(id)['downloadCredit'] < 1)return false
|
||||||
|
if(stmt_getFreeze.get(id)['freeze'] == 1)return false
|
||||||
|
return true
|
||||||
|
}catch(e){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const db_updateDownloadCredit = (id)=>{
|
||||||
|
try{
|
||||||
|
stmt_updateDownloadCredit.run(id)
|
||||||
|
return true
|
||||||
|
}catch(e){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const db_getTimestamp = (id)=>{
|
||||||
|
return stmt_getTimestamp.get(id)['timestamp']
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新存储的文件,删除已经被冻结的文件以及相关记录,然后冻结过期文件
|
||||||
|
const updateFiles = ()=>{
|
||||||
|
try{
|
||||||
|
let rows = stmt_getFreezenIDs.all()
|
||||||
|
stmt_deleteExpireData.run()
|
||||||
|
for(let row of rows){
|
||||||
|
let fileName = row['id']
|
||||||
|
let path = fileDir + '/' + fileName
|
||||||
|
try{
|
||||||
|
fs.unlink(path,(e)=>{if(e)throw e})
|
||||||
|
}catch(e){
|
||||||
|
console.log(e)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stmt_freezeExpireData.run(parseInt(Date.now()) - LifeOfFile)
|
||||||
|
}catch(e){
|
||||||
|
console.log(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateFiles()
|
||||||
|
// 3小时更新一次
|
||||||
|
setInterval(updateFiles,1000*3600*3)
|
||||||
|
|
||||||
|
|
||||||
|
var app = express();
|
||||||
|
app.use(cors());
|
||||||
|
var tempMap = {}
|
||||||
|
|
||||||
|
if (!fs.existsSync(fileDir)) {
|
||||||
|
fs.mkdirSync(fileDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateId(){
|
||||||
|
var returnStr = "",
|
||||||
|
range = 8,
|
||||||
|
charStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||||
|
|
||||||
|
for(var i=0; i<range; i++){
|
||||||
|
var index = Math.round(Math.random() * 35);
|
||||||
|
returnStr += charStr.substring(index,index+1);
|
||||||
|
}
|
||||||
|
return returnStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传文件接口
|
||||||
|
function uploadFile(req, res, fileName, downloadCredit){
|
||||||
|
let id = generateId();
|
||||||
|
const fileStream = fs.createWriteStream(fileDir + '/' + id);
|
||||||
|
db_insert(id,fileName,downloadCredit)
|
||||||
|
// tempMap[id] = fileName;
|
||||||
|
// console.log(tempMap);
|
||||||
|
req.pipe(fileStream);
|
||||||
|
res.on('data',(c)=>{console.log(c)})
|
||||||
|
req.on("end", function () {
|
||||||
|
fileStream.end();
|
||||||
|
res.statusCode = 200;
|
||||||
|
res.end(id);
|
||||||
|
});
|
||||||
|
req.on("error", err => {
|
||||||
|
fileStream.end();
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end("File save failed: " + err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadFile(req, res, fileId){
|
||||||
|
try {
|
||||||
|
if(db_downloadable(fileId) == false){
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end(null);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db_updateDownloadCredit(fileId)
|
||||||
|
var fileName = db_getFileName(fileId)
|
||||||
|
if(fileName == null){
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end(null);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let data = fs.readFileSync(fileDir + '/' + fileId);
|
||||||
|
res.writeHead(200, {
|
||||||
|
"Content-Type": "application/octet-stream ",
|
||||||
|
"File-Name": fileName,
|
||||||
|
'Content-Length':data.length,
|
||||||
|
});
|
||||||
|
res.end(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置一些通用的中间件
|
||||||
|
// app.use(bodyParser.json());
|
||||||
|
// app.use(bodyParser.urlencoded({extended: true}));
|
||||||
|
|
||||||
|
// app.all()
|
||||||
|
app.post('/file/upload', function(req, res){
|
||||||
|
res.header("Access-Control-Allow-Origin", "*");
|
||||||
|
res.header("Access-Control-Allow-Headers", "*");
|
||||||
|
res.header("Access-Control-Allow-Methods", "POST");
|
||||||
|
res.header("Access-Control-Expose-Headers", "File-Name");
|
||||||
|
var fileSize = parseInt(req.get('Content-Length'))
|
||||||
|
var fileName = req.query.fileName;
|
||||||
|
var downloadCredit = parseInt(req.query.downloadCredit);
|
||||||
|
if(downloadCredit == -1)downloadCredit = 2147483647
|
||||||
|
if(!fileName){
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end("FileName Not Found");
|
||||||
|
}else if(fileSize > uploadMaxSize){
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end("Maximum size limit exceeded");
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
uploadFile(req, res, fileName, downloadCredit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/file/download', 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{
|
||||||
|
downloadFile(req, res, fileId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动服务器
|
||||||
|
app.listen(PORT, HOST, function(){
|
||||||
|
// console.log('Example app listening on port ' + PORT + '.');
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import express from 'express'
|
||||||
|
|
||||||
|
const HOST = '0.0.0.0'
|
||||||
|
const PORT = '17057'
|
||||||
|
const PATH = "./dist";
|
||||||
|
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(express.static(PATH));
|
||||||
|
|
||||||
|
app.listen(PORT, HOST, function(){
|
||||||
|
console.log('Example app listening on port ' + PORT + '.');
|
||||||
|
});
|
||||||
|
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
<script setup>
|
||||||
|
import uploadFileMiao from "./components/uploadFileMiao.vue";
|
||||||
|
import downloadFileMiao from "./components/downloadFileMiao.vue";
|
||||||
|
import settingMiao from "./components/setting.vue";
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="windows">
|
||||||
|
<n-notification-provider placement="bottom-right" :max="3">
|
||||||
|
<n-message-provider>
|
||||||
|
<n-card content-style="padding: 0; margin:0;" id="theCard">
|
||||||
|
<n-tabs type="line" size="large" :tabs-padding="20" pane-style="padding: 20px;">
|
||||||
|
<n-tab-pane name="上传文件">
|
||||||
|
<uploadFileMiao style />
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="下载文件">
|
||||||
|
<downloadFileMiao style/>
|
||||||
|
</n-tab-pane>
|
||||||
|
<n-tab-pane name="设置">
|
||||||
|
<settingMiao style="max-width:100%" />
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</n-card>
|
||||||
|
</n-message-provider>
|
||||||
|
</n-notification-provider>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#windows {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0%;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
#theCard {
|
||||||
|
width: 50%;
|
||||||
|
height: 80%;
|
||||||
|
}
|
||||||
|
@media screen and (max-width:768px) {
|
||||||
|
#theCard {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/* color palette from <https://github.com/vuejs/theme> */
|
||||||
|
:root {
|
||||||
|
--vt-c-white: #ffffff;
|
||||||
|
--vt-c-white-soft: #f8f8f8;
|
||||||
|
--vt-c-white-mute: #f2f2f2;
|
||||||
|
|
||||||
|
--vt-c-black: #181818;
|
||||||
|
--vt-c-black-soft: #222222;
|
||||||
|
--vt-c-black-mute: #282828;
|
||||||
|
|
||||||
|
--vt-c-indigo: #2c3e50;
|
||||||
|
|
||||||
|
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||||
|
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||||
|
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||||
|
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||||
|
|
||||||
|
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||||
|
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||||
|
--vt-c-text-dark-1: var(--vt-c-white);
|
||||||
|
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* semantic color variables for this project */
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-white);
|
||||||
|
--color-background-soft: var(--vt-c-white-soft);
|
||||||
|
--color-background-mute: var(--vt-c-white-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-light-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-light-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-light-1);
|
||||||
|
--color-text: var(--vt-c-text-light-1);
|
||||||
|
|
||||||
|
--section-gap: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--color-background: var(--vt-c-black);
|
||||||
|
--color-background-soft: var(--vt-c-black-soft);
|
||||||
|
--color-background-mute: var(--vt-c-black-mute);
|
||||||
|
|
||||||
|
--color-border: var(--vt-c-divider-dark-2);
|
||||||
|
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||||
|
|
||||||
|
--color-heading: var(--vt-c-text-dark-1);
|
||||||
|
--color-text: var(--vt-c-text-dark-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
min-height: 100vh;
|
||||||
|
color: var(--color-text);
|
||||||
|
background: var(--color-background);
|
||||||
|
transition:
|
||||||
|
color 0.5s,
|
||||||
|
background-color 0.5s;
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family:
|
||||||
|
Inter,
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
'Segoe UI',
|
||||||
|
Roboto,
|
||||||
|
Oxygen,
|
||||||
|
Ubuntu,
|
||||||
|
Cantarell,
|
||||||
|
'Fira Sans',
|
||||||
|
'Droid Sans',
|
||||||
|
'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
font-size: 15px;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||||
|
After Width: | Height: | Size: 276 B |
@@ -0,0 +1,35 @@
|
|||||||
|
@import './base.css';
|
||||||
|
|
||||||
|
#app {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
.green {
|
||||||
|
text-decoration: none;
|
||||||
|
color: hsla(160, 100%, 37%, 1);
|
||||||
|
transition: 0.4s;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
a:hover {
|
||||||
|
background-color: hsla(160, 100%, 37%, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
<template>
|
||||||
|
<div style="width:auto">
|
||||||
|
<!-- <p>downloadProcess:{{ downloadProcess }}%</p>
|
||||||
|
<button @click="downloadFile">下载</button>-->
|
||||||
|
<n-input
|
||||||
|
v-model:value="downloadFileID"
|
||||||
|
type="textarea"
|
||||||
|
placeholder="于此处粘贴输入取件码 多行也能正确识别"
|
||||||
|
:autosize="{
|
||||||
|
minRows: 3,
|
||||||
|
maxRows: 5
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<n-flex justify="end" style="margin: 10px 0;position:relative">
|
||||||
|
<n-button type="error" @click="downloadFileID = null">清空输入</n-button>
|
||||||
|
<n-button type="primary" @click="praseInput">开始下载</n-button>
|
||||||
|
</n-flex>
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="downloadQuene"
|
||||||
|
:max-height="250"
|
||||||
|
:bordered="false"
|
||||||
|
:single-line="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import aesjs from "aes-js";
|
||||||
|
import { Base64 } from "js-base64";
|
||||||
|
import { saveAs } from "file-saver";
|
||||||
|
import { useSettingStore } from "@/pinia/store";
|
||||||
|
import { sm4 } from "sm-crypto";
|
||||||
|
|
||||||
|
var downloadFileID = ref();
|
||||||
|
|
||||||
|
const setting = useSettingStore();
|
||||||
|
|
||||||
|
// 服务端端口
|
||||||
|
const serverPort = setting.serverPort;
|
||||||
|
|
||||||
|
var baseUrl =
|
||||||
|
location.protocol + "//" + location.hostname + ":" + serverPort + "/";
|
||||||
|
// console.log(baseUrl);
|
||||||
|
|
||||||
|
const blob2Uint8Array = function(blob) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.readAsArrayBuffer(blob);
|
||||||
|
reader.onload = function() {
|
||||||
|
let buffer = reader.result;
|
||||||
|
let uint8array = new Uint8Array(buffer);
|
||||||
|
resolve(uint8array);
|
||||||
|
};
|
||||||
|
reader.onerror = function() {
|
||||||
|
reject(reader.error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const decrypt = (key, data) => {
|
||||||
|
return blob2Uint8Array(data).then(data => {
|
||||||
|
// 无加密
|
||||||
|
if(key.length === 0){
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
// SM4
|
||||||
|
if (key.length >= 34) {
|
||||||
|
key = key.substring(3);
|
||||||
|
// console.log([...data])
|
||||||
|
var decryptedBytes = new Uint8Array(
|
||||||
|
sm4.decrypt([...data], key, { output: "array" })
|
||||||
|
);
|
||||||
|
return decryptedBytes;
|
||||||
|
} else {
|
||||||
|
// AES
|
||||||
|
key = aesjs.utils.utf8.toBytes(key);
|
||||||
|
var aesCtr = new aesjs.ModeOfOperation.ctr(
|
||||||
|
key,
|
||||||
|
new aesjs.Counter(5)
|
||||||
|
);
|
||||||
|
var decryptedBytes = aesCtr.decrypt(data);
|
||||||
|
return decryptedBytes;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const decryptString = (key, str) => {
|
||||||
|
// 无加密
|
||||||
|
if(key.length === 0){
|
||||||
|
return sm4.decrypt(str, 'miaospringmiaospringmiaospringao');
|
||||||
|
}
|
||||||
|
// SM4
|
||||||
|
// console.log(key,key.length)
|
||||||
|
if (key.length >= 34) {
|
||||||
|
key = key.substring(3);
|
||||||
|
return sm4.decrypt(str, key);
|
||||||
|
}
|
||||||
|
// AES
|
||||||
|
var data = new Uint8Array(
|
||||||
|
Base64.decode(str)
|
||||||
|
.split(",")
|
||||||
|
.map(n => parseInt(n))
|
||||||
|
);
|
||||||
|
key = aesjs.utils.utf8.toBytes(key);
|
||||||
|
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5));
|
||||||
|
var decryptedBytes = aesCtr.decrypt(data);
|
||||||
|
var decryptedStr = aesjs.utils.utf8.fromBytes(decryptedBytes);
|
||||||
|
return decryptedStr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadFromUint8Array = (data, fileName) => {
|
||||||
|
let uint8Array = data;
|
||||||
|
let blob = new Blob([uint8Array], { type: "text/plain" });
|
||||||
|
// 使用file-saver.js
|
||||||
|
saveAs(blob, fileName);
|
||||||
|
|
||||||
|
//let url = URL.createObjectURL(blob);
|
||||||
|
//let a = document.createElement("a");
|
||||||
|
//a.href = url;
|
||||||
|
//a.download = fileName;
|
||||||
|
//a.click();
|
||||||
|
//URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
// 刷新展示的下载进度
|
||||||
|
const updateDownloadQuene = (code, key, value) => {
|
||||||
|
for (let index in downloadQuene.value) {
|
||||||
|
if (downloadQuene.value[index].code == code) {
|
||||||
|
downloadQuene.value[index][key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const praseInput = () => {
|
||||||
|
let codeArray = downloadFileID.value.split("\n");
|
||||||
|
for (let code of codeArray) {
|
||||||
|
if (
|
||||||
|
code === "" ||
|
||||||
|
downloadQuene.value.filter(m => m.code == code).length !== 0
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
// console.log(code);
|
||||||
|
let dp = {
|
||||||
|
code: code,
|
||||||
|
fileName: "有的事总是不得而知",
|
||||||
|
downloadProcess: "连接中。。。"
|
||||||
|
};
|
||||||
|
downloadQuene.value.push(dp);
|
||||||
|
downloadFile(code);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadFile = code => {
|
||||||
|
var id = code.substring(0, 8);
|
||||||
|
var key = code.substring(8);
|
||||||
|
var fileName;
|
||||||
|
const params = {
|
||||||
|
fileName: id
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
|
method: "GET"
|
||||||
|
};
|
||||||
|
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", `已完成`);
|
||||||
|
downloadFromUint8Array(data, fileName);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
updateDownloadQuene(
|
||||||
|
code,
|
||||||
|
"fileName",
|
||||||
|
`出错,可能是文件过期或下载次数用完`
|
||||||
|
);
|
||||||
|
updateDownloadQuene(code, "downloadProcess", `出错`);
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 下载队列
|
||||||
|
const downloadQuene = ref([]);
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "取件码",
|
||||||
|
key: "code",
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "文件名",
|
||||||
|
key: "fileName",
|
||||||
|
width: 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "下载状态",
|
||||||
|
key: "downloadProcess",
|
||||||
|
width: 150
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// 进度条进度
|
||||||
|
const progressPercentage = ref(60);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-list hoverable>
|
||||||
|
<n-list-item>
|
||||||
|
<p style="font-size:larger;position:absolute;left:5px;">选择加密算法:</p>
|
||||||
|
<br />
|
||||||
|
<!-- <br /> -->
|
||||||
|
<!-- <br /> -->
|
||||||
|
<n-flex justify="end">
|
||||||
|
<span v-for="f in fArray" style="margin:6px 5px -4px 0;">
|
||||||
|
<n-button
|
||||||
|
:type="f == activeFuction ? 'primary' : 'default'"
|
||||||
|
@click="setActiveF(f)"
|
||||||
|
>{{ f }}</n-button>
|
||||||
|
</span>
|
||||||
|
</n-flex>
|
||||||
|
</n-list-item>
|
||||||
|
<n-list-item>
|
||||||
|
<p style="font-size:larger;position:absolute;left:5px;">选择密钥组成:</p>
|
||||||
|
<br />
|
||||||
|
<n-flex justify="end">
|
||||||
|
<span v-for="k in keyButtons" style="margin:5px">
|
||||||
|
<n-button
|
||||||
|
:type="activeKeyButtons.includes(k) && activeFuction !== '不加密' ? 'primary' : 'default'"
|
||||||
|
:disabled="activeFuction === '不加密'"
|
||||||
|
@click="setActiveK(k)"
|
||||||
|
>{{ k }}</n-button>
|
||||||
|
</span>
|
||||||
|
</n-flex justify="end">
|
||||||
|
</n-list-item>
|
||||||
|
</n-list>
|
||||||
|
</div>
|
||||||
|
<div style="width: 100vw;height:100%"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import { useSettingStore } from "@/pinia/store";
|
||||||
|
import { useMessage, useNotification } from "naive-ui";
|
||||||
|
|
||||||
|
const setting = useSettingStore();
|
||||||
|
const message = useMessage();
|
||||||
|
// const notification = useNotification();
|
||||||
|
|
||||||
|
const fArray = ref(["AES128", "AES256", "SM4", "不加密"]);
|
||||||
|
const activeFuction = ref(setting.activeFuction);
|
||||||
|
const keyButtons = ref(["a-z", "A-Z", "0-9"]);
|
||||||
|
const activeKeyButtons = ref(setting.activeKeyMap);
|
||||||
|
console.log(
|
||||||
|
activeFuction.value,
|
||||||
|
activeKeyButtons.value,
|
||||||
|
activeKeyButtons.value.includes("A-Z")
|
||||||
|
);
|
||||||
|
function setActiveF(f) {
|
||||||
|
activeFuction.value = f;
|
||||||
|
setting.activeFuction = f;
|
||||||
|
localStorage.setItem("activeFuction", f);
|
||||||
|
}
|
||||||
|
function setActiveK(k) {
|
||||||
|
if (activeKeyButtons.value.includes(k)) {
|
||||||
|
if (activeKeyButtons.value.length <= 1) {
|
||||||
|
return message.info(`至少选择1项密钥组成`);
|
||||||
|
}
|
||||||
|
activeKeyButtons.value = activeKeyButtons.value.filter(v => v != k);
|
||||||
|
} else {
|
||||||
|
activeKeyButtons.value.push(k);
|
||||||
|
}
|
||||||
|
setting.activeKeyMap = activeKeyButtons.value;
|
||||||
|
localStorage.setItem(
|
||||||
|
"activeKeyMap",
|
||||||
|
JSON.stringify(activeKeyButtons.value)
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
activeFuction.value,
|
||||||
|
activeKeyButtons.value,
|
||||||
|
activeKeyButtons.value.includes("A-Z")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<n-upload
|
||||||
|
ref="uploadRef"
|
||||||
|
:default-upload="false"
|
||||||
|
multiple
|
||||||
|
:custom-request="customRequest"
|
||||||
|
@before-upload="beforeUpload"
|
||||||
|
>
|
||||||
|
<n-upload-dragger>
|
||||||
|
<!-- <div style="margin-bottom: 12px">
|
||||||
|
<n-icon size="48" :depth="3"></n-icon>
|
||||||
|
</div>-->
|
||||||
|
<n-text style="font-size: 16px">点击此处或者拖动文件到该区域来上传</n-text>
|
||||||
|
<n-p depth="3" style="margin: 8px 0 0 0">
|
||||||
|
最大支持200MB大小文件,其实只是懒得做分片上传而已
|
||||||
|
<br />虽然理论上没有人有能力审查你上传的东西
|
||||||
|
<br />反正没人用写这么多干什么。。。
|
||||||
|
</n-p>
|
||||||
|
</n-upload-dragger>
|
||||||
|
</n-upload>
|
||||||
|
|
||||||
|
<n-flex justify="end" style="margin: 10px 0;position:relative">
|
||||||
|
<n-input-number v-model:value="downloadCredit" placeholder="可下载次数,默认无限" clearable :min="1" :max="2147483647" style="width:max-content"/>
|
||||||
|
<n-button type="primary" @click="clickUpload">开始上传</n-button>
|
||||||
|
</n-flex>
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="file_Code_Map"
|
||||||
|
:row-props="rowProps"
|
||||||
|
:max-height="250"
|
||||||
|
:bordered="false"
|
||||||
|
:single-line="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import aesjs from "aes-js";
|
||||||
|
import { Base64 } from "js-base64";
|
||||||
|
import { useMessage, useNotification } from "naive-ui";
|
||||||
|
import useClipboard from "vue-clipboard3";
|
||||||
|
import { useSettingStore } from "@/pinia/store";
|
||||||
|
import { sm4 } from 'sm-crypto'
|
||||||
|
|
||||||
|
const setting = useSettingStore();
|
||||||
|
const { toClipboard } = useClipboard();
|
||||||
|
const message = useMessage();
|
||||||
|
const notification = useNotification();
|
||||||
|
|
||||||
|
// 目前使用加密
|
||||||
|
const activeFuction = setting.activeFuction;
|
||||||
|
// 启用的密钥组成
|
||||||
|
const activeKeyButtons = setting.activeKeyMap;
|
||||||
|
// 服务端端口
|
||||||
|
const serverPort = setting.serverPort;
|
||||||
|
|
||||||
|
// console.log(activeFuction,activeKeyButtons)
|
||||||
|
var charMap = ""
|
||||||
|
for(let i of setting.activeKeyMap){
|
||||||
|
if(i === 'A-Z'){
|
||||||
|
charMap = charMap + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
||||||
|
}else if(i === "a-z"){
|
||||||
|
charMap = charMap + 'abcdefghijklmnopqrstuvwxyz'
|
||||||
|
}else if(i === '0-9'){
|
||||||
|
charMap = charMap + '0123456789'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 生成密钥
|
||||||
|
const generateKey = function() {
|
||||||
|
if(setting.activeFuction === "不加密"){
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
var returnStr = "",
|
||||||
|
length = setting.activeFuction === "AES128" ? 16 : 32
|
||||||
|
// charMap = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
var index = Math.round(Math.random() * charMap.length);
|
||||||
|
returnStr += charMap.substring(index, index + 1);
|
||||||
|
}
|
||||||
|
return returnStr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 上传文件用
|
||||||
|
const uploadRef = ref(null);
|
||||||
|
|
||||||
|
// 分享码可用次数
|
||||||
|
const downloadCredit = ref(null);
|
||||||
|
|
||||||
|
var file_Code_Map = ref([]);
|
||||||
|
|
||||||
|
var uploadProcess = ref(0);
|
||||||
|
|
||||||
|
|
||||||
|
const baseUrl =
|
||||||
|
location.protocol + "//" + location.hostname + ":" + serverPort + "/";
|
||||||
|
|
||||||
|
|
||||||
|
const blob2Uint8Array = function(blob) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.readAsArrayBuffer(blob);
|
||||||
|
reader.onload = function() {
|
||||||
|
let buffer = reader.result;
|
||||||
|
let uint8array = new Uint8Array(buffer);
|
||||||
|
resolve(uint8array);
|
||||||
|
};
|
||||||
|
reader.onerror = function() {
|
||||||
|
reject(reader.error);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// 包含AES128和AES256
|
||||||
|
const AES = (key,data)=>{
|
||||||
|
key = aesjs.utils.utf8.toBytes(key);
|
||||||
|
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5));
|
||||||
|
var encryptedBytes = aesCtr.encrypt(data);
|
||||||
|
return encryptedBytes;
|
||||||
|
}
|
||||||
|
const SM4 = (key,data)=>{
|
||||||
|
// console.log(key)
|
||||||
|
var encryptedBytes = sm4.encrypt(data,key,{padding: 'pkcs#7',output:'array'});
|
||||||
|
// console.log(encryptedBytes, sm4.decrypt(encryptedBytes, key,{output:'array'}))
|
||||||
|
return new Uint8Array(encryptedBytes);
|
||||||
|
}
|
||||||
|
const encrypt = (key, data, fileName) => {
|
||||||
|
return blob2Uint8Array(data).then(data => {
|
||||||
|
let encrypted_data = null
|
||||||
|
if(setting.activeFuction === "不加密"){
|
||||||
|
encrypted_data = data
|
||||||
|
}else if(setting.activeFuction != "SM4"){
|
||||||
|
message.info(`开始加密${fileName}`);
|
||||||
|
encrypted_data = AES(key,data)
|
||||||
|
message.info(`${fileName}加密完成`);
|
||||||
|
}else{
|
||||||
|
message.info(`开始加密${fileName}`);
|
||||||
|
encrypted_data = SM4(key,data)
|
||||||
|
message.info(`${fileName}加密完成`);
|
||||||
|
}
|
||||||
|
return encrypted_data
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const encryptString = (key, str) => {
|
||||||
|
// console.log(key)
|
||||||
|
if(setting.activeFuction === "不加密"){
|
||||||
|
return sm4.encrypt(str, 'miaospringmiaospringmiaospringao')
|
||||||
|
}else if(setting.activeFuction !== "SM4"){
|
||||||
|
key = aesjs.utils.utf8.toBytes(key);
|
||||||
|
var data = aesjs.utils.utf8.toBytes(str);
|
||||||
|
var aesCtr = new aesjs.ModeOfOperation.ctr(key, new aesjs.Counter(5));
|
||||||
|
var encryptedBytes = aesCtr.encrypt(data);
|
||||||
|
return Base64.encode(encryptedBytes);
|
||||||
|
}else{
|
||||||
|
return sm4.encrypt(str, key)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clickUpload = function(){
|
||||||
|
// for(let i of uploadRef.value)console.log(i)
|
||||||
|
// console.log(uploadRef.value)
|
||||||
|
message.info(`开始上传`);
|
||||||
|
uploadRef.value.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const beforeUpload = async function(data) {
|
||||||
|
// console.log(data.file.file.size)
|
||||||
|
if (data.file.file.size > 200 * 1024 * 1024) {
|
||||||
|
message.error(`${data.file.file.name}的大小超过限制,无法上传`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const customRequest = ({
|
||||||
|
file,
|
||||||
|
data,
|
||||||
|
headers,
|
||||||
|
withCredentials,
|
||||||
|
action,
|
||||||
|
onFinish,
|
||||||
|
onError,
|
||||||
|
onProgress
|
||||||
|
}) => {
|
||||||
|
// console.log(file);
|
||||||
|
file = file.file;
|
||||||
|
var fileName = file.name;
|
||||||
|
const key = generateKey();
|
||||||
|
const encryptedFileName = encryptString(key, fileName);
|
||||||
|
var f = new Blob([file]);
|
||||||
|
encrypt(key, f, fileName).then(data => {
|
||||||
|
const params = {
|
||||||
|
fileName: encryptedFileName,
|
||||||
|
downloadCredit:downloadCredit.value == null ? -1 : downloadCredit.value
|
||||||
|
};
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.upload.addEventListener("progress", function(event) {
|
||||||
|
const percent = (event.loaded / event.total) * 100;
|
||||||
|
uploadProcess.value = percent.toFixed(2);
|
||||||
|
onProgress({ percent: Math.ceil(percent) });
|
||||||
|
});
|
||||||
|
onProgress({ percent: 1 });
|
||||||
|
xhr.open(
|
||||||
|
"POST",
|
||||||
|
baseUrl + "file/upload?" + new URLSearchParams(params)
|
||||||
|
);
|
||||||
|
xhr.send(data);
|
||||||
|
xhr.onreadystatechange = () => {
|
||||||
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||||
|
let id = xhr.responseText;
|
||||||
|
let code = id + key
|
||||||
|
if(setting.activeFuction === "SM4"){
|
||||||
|
code = id + 'SM4' + key
|
||||||
|
}
|
||||||
|
file_Code_Map.value.push({
|
||||||
|
code: code,
|
||||||
|
fileName: fileName
|
||||||
|
});
|
||||||
|
notification["success"]({
|
||||||
|
content: "上传完成",
|
||||||
|
meta: `${fileName} 已成功上传`,
|
||||||
|
duration: 2500,
|
||||||
|
keepAliveOnHover: true
|
||||||
|
});
|
||||||
|
onFinish();
|
||||||
|
} else {
|
||||||
|
console.error(xhr.statusText);
|
||||||
|
onError();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "文件名",
|
||||||
|
key: "fileName",
|
||||||
|
width: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "取件码(点击复制)",
|
||||||
|
key: "code"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 记录是否复制过,第一次复制的时候提醒可以连续复制
|
||||||
|
var copyedArray = [];
|
||||||
|
const rowProps = row => {
|
||||||
|
return {
|
||||||
|
style: "cursor: pointer;",
|
||||||
|
onClick: () => {
|
||||||
|
toClipboard(`${copyedArray.join('\n')}\n${row.code}`.trim()).then(() => {
|
||||||
|
message.info(`已复制 ${row.fileName} 的取件码至剪切板`);
|
||||||
|
if (copyedArray.length == 0) {
|
||||||
|
message.info(
|
||||||
|
`可以试试点击其他取件码,也许这个玩意会自动处理剪切板内容`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
copyedArray.push(row.code)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import "./assets/main.css";
|
||||||
|
import 'vfonts/Lato.css'
|
||||||
|
|
||||||
|
import { createApp } from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import router from "./router";
|
||||||
|
import naive from "naive-ui";
|
||||||
|
import { createPinia } from "pinia";
|
||||||
|
|
||||||
|
// const naive = create({
|
||||||
|
// components: [NButton,NUpload,NP,NText]
|
||||||
|
// });
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
const app = createApp(App);
|
||||||
|
|
||||||
|
app.use(router);
|
||||||
|
app.use(pinia);
|
||||||
|
app.use(naive);
|
||||||
|
|
||||||
|
app.mount("#app");
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
|
|
||||||
|
var activeFuctionLocal = localStorage.getItem('activeFuction')
|
||||||
|
if (activeFuctionLocal == null){
|
||||||
|
activeFuctionLocal = 'AES128'
|
||||||
|
localStorage.setItem('activeFuction',activeFuctionLocal)
|
||||||
|
}
|
||||||
|
// localStorage.removeItem('activeKeyMap')
|
||||||
|
var activeKeyMap = localStorage.getItem('activeKeyMap')
|
||||||
|
|
||||||
|
if (activeKeyMap == null ){
|
||||||
|
activeKeyMap = JSON.stringify(['A-Z','0-9'])
|
||||||
|
localStorage.setItem('activeKeyMap',activeKeyMap)
|
||||||
|
}
|
||||||
|
export const useSettingStore = defineStore("setting", {
|
||||||
|
state: () => ({
|
||||||
|
activeFuction: activeFuctionLocal,
|
||||||
|
activeKeyMap:JSON.parse(activeKeyMap),
|
||||||
|
serverPort:"17058"
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
// double: state => state.count * 2
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
// increment() {
|
||||||
|
// this.count++;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
// import HomeView from '../views/HomeView.vue'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
// {
|
||||||
|
// path: '/',
|
||||||
|
// name: 'home',
|
||||||
|
// component: HomeView
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: '/about',
|
||||||
|
// name: 'about',
|
||||||
|
// // route level code-splitting
|
||||||
|
// // this generates a separate chunk (About.[hash].js) for this route
|
||||||
|
// // which is lazy-loaded when the route is visited.
|
||||||
|
// component: () => import('../views/AboutView.vue')
|
||||||
|
// }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
vue(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server:{
|
||||||
|
host:'0.0.0.0',
|
||||||
|
port:'17057',
|
||||||
|
open:true,
|
||||||
|
},
|
||||||
|
})
|
||||||
Reference in new issue
Block a user