first commit
This commit is contained in:
commit
61d2037ca0
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||
}
|
||||
7
README.md
Normal file
7
README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
16
index.html
Normal file
16
index.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SNS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div class="dom"></div>
|
||||
<div id="app">
|
||||
|
||||
</div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
2227
package-lock.json
generated
Normal file
2227
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "sns",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"@icon-park/vue-next": "^1.4.2",
|
||||
"axios": "^1.5.0",
|
||||
"echarts": "^5.4.3",
|
||||
"element-plus": "^2.3.12",
|
||||
"qs": "^6.11.2",
|
||||
"unplugin-icons": "^0.17.1",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.5",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"unplugin-auto-import": "^0.16.7",
|
||||
"unplugin-vue-components": "^0.25.2",
|
||||
"vite": "^4.4.5"
|
||||
}
|
||||
}
|
||||
1
public/vite.svg
Normal file
1
public/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
11
src/App.vue
Normal file
11
src/App.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view>
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
60
src/Util/index.js
Normal file
60
src/Util/index.js
Normal file
@ -0,0 +1,60 @@
|
||||
let KB = 1024
|
||||
let MB = KB * 1024
|
||||
let GB = MB * 1024
|
||||
|
||||
let Util = {
|
||||
formatSize:function (limit) {
|
||||
let size;
|
||||
if (limit < 0.1 * 1024) { //小于0.1KB,则转化成B
|
||||
size = limit.toFixed(2) + "B"
|
||||
} else if (limit < 0.1 * 1024 * 1024) { //小于0.1MB,则转化成KB
|
||||
size = (limit / 1024).toFixed(2) + "KB"
|
||||
} else if (limit < 0.1 * 1024 * 1024 * 1024) { //小于0.1GB,则转化成MB
|
||||
size = (limit / (1024 * 1024)).toFixed(2) + "MB"
|
||||
} else { //其他转化成GB
|
||||
size = (limit / (1024 * 1024 * 1024)).toFixed(2) + "GB"
|
||||
}
|
||||
|
||||
let sizeStr = size + ""; //转成字符串
|
||||
let index = sizeStr.indexOf("."); //获取小数点处的索引
|
||||
let dou = sizeStr.substring(index + 1, 2) //获取小数点后两位的值
|
||||
if (dou === "00") { //判断后两位是否为00,如果是则删除00
|
||||
return sizeStr.substring(0, index) + sizeStr.substring(index + 3, 2)
|
||||
}
|
||||
return size;
|
||||
},
|
||||
|
||||
formatDuration: function (timestamp) {
|
||||
let seconds = timestamp
|
||||
let str = ''
|
||||
if (seconds > 3600 * 24) {
|
||||
let days = Math.floor(seconds / (3600 * 24))
|
||||
seconds = seconds % 3600 * 24
|
||||
str += `${days}天`
|
||||
}
|
||||
if (seconds > 3600) {
|
||||
let hours = Math.floor(seconds / 3600)
|
||||
seconds = seconds % 3600
|
||||
str += `${hours}时`
|
||||
}
|
||||
if (seconds > 60) {
|
||||
let minutes = Math.floor(seconds / 60)
|
||||
str += `${minutes}分`
|
||||
}
|
||||
return str
|
||||
},
|
||||
|
||||
formatDate: function (time) {
|
||||
return new Date(time).toLocaleString('zh-CN')
|
||||
},
|
||||
KB, MB, GB,
|
||||
colors: [
|
||||
{ color: '#f56c6c', percentage: 90 },
|
||||
{ color: '#e6a23c', percentage: 80 },
|
||||
{ color: '#6f7ad3', percentage: 60 },
|
||||
{ color: '#1989fa', percentage: 40 },
|
||||
{ color: '#5cb87a', percentage: 20 },
|
||||
],
|
||||
}
|
||||
|
||||
export default Util
|
||||
74
src/components/adjust-share/index.vue
Normal file
74
src/components/adjust-share/index.vue
Normal file
@ -0,0 +1,74 @@
|
||||
<script setup>
|
||||
import Util from "../../Util/index.js";
|
||||
import {computed, ref} from "vue";
|
||||
import axios from "axios";
|
||||
import store from "../../store/index.js";
|
||||
import qs from "qs";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
let props = defineProps(['isAdjustingShare', 'currentFile', 'currentSiteId'])
|
||||
let emits = defineEmits(['close'])
|
||||
let isAdjustingShare = computed(() => {
|
||||
return props.isAdjustingShare
|
||||
})
|
||||
let isAdmin = computed(() => {
|
||||
return store.state.config.isAdmin
|
||||
})
|
||||
let expireHour = ref(null)
|
||||
let count = ref(null)
|
||||
|
||||
function adjustShare() {
|
||||
let shareCode = props.currentFile.shareCode
|
||||
if(isAdmin.value)
|
||||
shareCode = props.currentSiteId + ":" + props.currentFile.shareCode
|
||||
axios.post(store.state.config.host + "share/adjust?", qs.stringify({shareCode, time:expireHour.value, count:count.value})).then((res) => {
|
||||
if(res.data.result === 'success')
|
||||
ElMessage.success(res.data.data)
|
||||
else
|
||||
ElMessage.error(res.data.data)
|
||||
close()
|
||||
})
|
||||
}
|
||||
function close(){
|
||||
emits('close')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog title="调整分享" v-model="isAdjustingShare" width="625px" @close="close">
|
||||
<el-row>
|
||||
<el-col :span="10">当前文件:{{currentFile.name}}</el-col>
|
||||
<el-col :span="14">(正数为添加,负数为减少,都留空则调整为永久分享)</el-col>
|
||||
</el-row>
|
||||
<br>
|
||||
<el-row>
|
||||
<el-col :span="10">
|
||||
过期时间:{{currentFile.expireTime === undefined?'不过期':Util.formatDate(currentFile.expireTime)}}
|
||||
</el-col>
|
||||
<el-input v-model="expireHour" style=" width: 300px">
|
||||
<template #prepend>
|
||||
分享时长
|
||||
</template>
|
||||
<template #append>
|
||||
小时
|
||||
</template>
|
||||
</el-input>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="10">
|
||||
剩余ip数:{{currentFile.availableCount === undefined?'不限制':currentFile.availableCount}}
|
||||
</el-col>
|
||||
<el-input v-model="count" style="width: 300px">
|
||||
<template #prepend>分享ip数</template>
|
||||
</el-input>
|
||||
</el-row>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="adjustShare">确定</el-button>
|
||||
<el-button @click="close">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
196
src/components/file-operation/index.vue
Normal file
196
src/components/file-operation/index.vue
Normal file
@ -0,0 +1,196 @@
|
||||
<script setup>
|
||||
import hardDisk from "@icon-park/vue-next/lib/icons/HardDisk.js";
|
||||
import {computed, ref, watch} from "vue";
|
||||
import store from "../../store/index.js";
|
||||
import axios from "axios";
|
||||
import qs from "qs";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {Folder} from "@element-plus/icons-vue";
|
||||
let props = defineProps(['isOperatingFile', 'operation', 'selectedFiles', 'currentSideId', 'currentPath', 'crossSite'])
|
||||
let emits = defineEmits(['close'])
|
||||
|
||||
//移动 复制 解包
|
||||
let isOperatingFile = computed(() => {
|
||||
return props.isOperatingFile
|
||||
})
|
||||
let isAdmin = computed(() => {
|
||||
return store.state.config.isAdmin
|
||||
})
|
||||
let title = computed(() => {
|
||||
switch (props.operation){
|
||||
case "move":
|
||||
return "移动到"
|
||||
case "copy":
|
||||
return "复制到"
|
||||
case "extract":
|
||||
return "解包到"
|
||||
}
|
||||
})
|
||||
let cursor = ref([])
|
||||
let files = ref([])
|
||||
let folders = computed(() => {
|
||||
let folders = []
|
||||
if(cursor.value.length === 0){
|
||||
onlineSites.value.forEach((site) => {
|
||||
folders.push({name:site.hostname, site:true, id:site.id})
|
||||
})
|
||||
} else {
|
||||
files.value.forEach((file) => {
|
||||
//去重,移动文件夹时不显示自身
|
||||
if (file.type === "FOLDER"){
|
||||
let push = true
|
||||
|
||||
props.selectedFiles.forEach((selectedFile) => {
|
||||
if(selectedFile.type === 'FOLDER' && selectedFile.path === file.path)
|
||||
push = false
|
||||
})
|
||||
|
||||
if(push)
|
||||
folders.push(file)
|
||||
}
|
||||
})
|
||||
}
|
||||
return folders;
|
||||
})
|
||||
let onlineSites = computed(() => {
|
||||
return store.getters.getOnlineSites
|
||||
})
|
||||
let currentPath = computed(() => {
|
||||
let path = ""
|
||||
for(let i=0; i<cursor.value.length; i++){
|
||||
if(i === 0){
|
||||
if(isAdmin.value)
|
||||
path = cursor.value[0] + ":"
|
||||
}else
|
||||
path += cursor.value[i] + "/"
|
||||
}
|
||||
return path
|
||||
})
|
||||
|
||||
//处理单击事件 文件夹或主机
|
||||
function handleClick(node) {
|
||||
if(node.name === '...')
|
||||
cursor.value.pop()
|
||||
else if(node.id !== undefined)
|
||||
cursor.value.push(node.id)
|
||||
else
|
||||
cursor.value.push(node.name)
|
||||
loadFilesByCursor()
|
||||
}
|
||||
//加载文件
|
||||
function loadFilesByCursor(){
|
||||
files.value.splice(0)
|
||||
//如果坐标为空,则放入服务器节点
|
||||
if (cursor.value.length === 0){
|
||||
onlineSites.value.forEach((site) => {
|
||||
files.value.push({name:site.hostname, site:true})
|
||||
})
|
||||
} else {
|
||||
axios.get(store.state.config.host + "file/get?" + qs.stringify({path:currentPath.value}))
|
||||
.then((res) => {
|
||||
let date = new Date()
|
||||
if(res.data.result === 'success'){
|
||||
if(cursor.value.length === 1) { //服务器
|
||||
if (isAdmin.value && props.crossSite)
|
||||
files.value.push({name: '...', type: 'FOLDER', size: 0, lastModify: date.getTime()})
|
||||
} else
|
||||
files.value.push({name: '...', type: 'FOLDER', size: 0, lastModify: date.getTime()})
|
||||
|
||||
files.value.push(...res.data.data)
|
||||
}else{
|
||||
ElMessage.error("文件查询失败:" + res.data.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
//跳转至之前的文件夹
|
||||
function jump(index){
|
||||
cursor.value.splice(index)
|
||||
loadFilesByCursor()
|
||||
}
|
||||
function close(){
|
||||
emits("close")
|
||||
}
|
||||
//确认该次操作:移动 打包 解包
|
||||
function confirm(){
|
||||
let source = props.currentPath
|
||||
let target = currentPath.value
|
||||
let fileNames = []
|
||||
props.selectedFiles.forEach((file) => {
|
||||
fileNames.push(file.name)
|
||||
})
|
||||
|
||||
//解压到目标路径的压缩包名称文件夹下
|
||||
if(props.operation === 'extract') {
|
||||
target += fileNames[0].replace(".tar", "") + "/"
|
||||
//如果target带有sourceId,则去除
|
||||
if(target.charAt(1) === ':')
|
||||
target.substring(2)
|
||||
}
|
||||
|
||||
//非管理员不用指定sourceId
|
||||
if(!isAdmin.value)
|
||||
source = source.substring(2)
|
||||
axios.post(store.state.config.host + `file/${props.operation}?`, qs.stringify({source, target, fileNames}, {indices:false}))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success')
|
||||
ElMessage.success(res.data.data)
|
||||
else
|
||||
ElMessage.error(res.data.data)
|
||||
})
|
||||
close()
|
||||
}
|
||||
watch(isOperatingFile, (value) => {
|
||||
if(value) {
|
||||
cursor.value.splice(0)
|
||||
if (!isAdmin.value || !props.crossSite)
|
||||
cursor.value[0] = props.currentSideId
|
||||
loadFilesByCursor()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog :title="title" v-model="isOperatingFile" top="0" @close="close" style="margin-bottom: 0">
|
||||
<!-- 不能跨站或不是管理员时,显示该面板-->
|
||||
<el-popover v-if="!crossSite || !isAdmin">
|
||||
<span v-if="isAdmin">
|
||||
选择文件夹或打、解包时不能跨服务器
|
||||
</span>
|
||||
<span v-else>
|
||||
用户不能跨服务器
|
||||
</span>
|
||||
|
||||
<template #reference>
|
||||
<el-link :underline="false" disabled>全部节点</el-link>
|
||||
</template>
|
||||
</el-popover>
|
||||
<el-link :underline="false" @click="jump(0)" :disabled="cursor.length === 0 || !crossSite" v-else>全部节点</el-link>
|
||||
<div v-for="(path, index) in cursor" style="display: inline-block">
|
||||
>
|
||||
<el-link :underline="false" :disabled="index === cursor.length - 1" @click="jump(index + 1)">{{path}}</el-link>
|
||||
</div>
|
||||
<el-table :data="folders" max-height="72vh" @rowClick="handleClick">
|
||||
<el-table-column width="50px">
|
||||
<template #default="scoped">
|
||||
<hardDisk theme="outline" size="24" fill="#333" v-if="scoped.row.site !== undefined"/>
|
||||
<el-icon v-else>
|
||||
<Folder/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="目标文件夹">
|
||||
<template #default="scoped">
|
||||
{{scoped.row.name}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button type="primary" :disabled="cursor.length === 0" @click="confirm">确定</el-button>
|
||||
<el-button @click="close">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
81
src/components/import-file/index.vue
Normal file
81
src/components/import-file/index.vue
Normal file
@ -0,0 +1,81 @@
|
||||
<script setup>
|
||||
import {ref, computed} from 'vue'
|
||||
import axios from "axios";
|
||||
import store from "../../store/index.js";
|
||||
import qs from "qs";
|
||||
import {ElMessage} from "element-plus";
|
||||
import Util from "../../Util/index.js";
|
||||
let props = defineProps(['isImportFile', 'path'])
|
||||
let emits = defineEmits(['close'])
|
||||
|
||||
let isImportFile = computed(() => {
|
||||
return props.isImportFile
|
||||
})
|
||||
let shareCode = ref('')
|
||||
let shareFile = ref(null)
|
||||
|
||||
function queryShareFile(){
|
||||
if(shareCode.value.trim() === '')
|
||||
ElMessage.error("请输入分享码")
|
||||
else if(!shareCode.value.includes(":") || !(shareCode.value.split(":").length === 2))
|
||||
ElMessage.error("分享码格式错误")
|
||||
else
|
||||
axios.get(store.state.config.host + "file/queryShareFile?" + qs.stringify({shareCode:shareCode.value}))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success'){
|
||||
shareFile.value = res.data.data
|
||||
ElMessage.success("查询成功")
|
||||
} else {
|
||||
ElMessage.error(res.data.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
function submit(){
|
||||
axios.post(store.state.config.host + "file/import?" + qs.stringify({shareCode:shareCode.value, path:props.path.substring(2)}))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success'){
|
||||
ElMessage.success(res.data.data)
|
||||
close()
|
||||
} else
|
||||
ElMessage.error(res.data.data)
|
||||
})
|
||||
}
|
||||
function close(){
|
||||
emits('close')
|
||||
shareCode.value = ""
|
||||
shareFile.value = null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog v-model="isImportFile" @close="close" title="导入文件">
|
||||
<el-row>
|
||||
<el-col :span="10">
|
||||
<el-input v-model="shareCode">
|
||||
<template #prepend>
|
||||
分享码
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<el-button @click="queryShareFile">查询文件</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div v-if="shareFile !== null">
|
||||
<el-row >
|
||||
<el-col>
|
||||
文件名称:{{shareFile.name}}<br>
|
||||
文件大小:{{Util.formatSize(shareFile.size)}}<br>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="submit" type="primary" :disabled="shareFile === null">确认导入</el-button>
|
||||
<el-button @click="close">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
19
src/components/minimize-str/index.vue
Normal file
19
src/components/minimize-str/index.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<script setup>
|
||||
defineProps(['str', 'length'])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-popover v-if="str.length > length" width="400px">
|
||||
{{str}}
|
||||
<template #reference>
|
||||
{{str.substring(0, length) + "..."}}
|
||||
</template>
|
||||
</el-popover>
|
||||
<span v-else>
|
||||
{{str}}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
87
src/components/share-file/index.vue
Normal file
87
src/components/share-file/index.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<script setup>
|
||||
import {computed, ref} from "vue";
|
||||
import axios from "axios";
|
||||
import store from "../../store/index.js";
|
||||
import qs from "qs";
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
let props = defineProps(['selectedFiles', 'isSharingFile', 'currentPath'])
|
||||
let emits = defineEmits(['close'])
|
||||
let isSharingFile = computed(() => {
|
||||
return props.isSharingFile
|
||||
})
|
||||
|
||||
let count = ref("")
|
||||
let expireHour = ref("")
|
||||
let isAdmin = computed(() => {
|
||||
return store.state.config.isAdmin
|
||||
})
|
||||
function close(){
|
||||
emits('close')
|
||||
}
|
||||
|
||||
function shareFile(){
|
||||
if(count.value < 0)
|
||||
ElMessage.error("分享ip数不能小于0")
|
||||
else if(expireHour.value < 0)
|
||||
ElMessage.error("过期时间不能小于0")
|
||||
else {
|
||||
let fileNames = []
|
||||
props.selectedFiles.forEach((file) => {
|
||||
fileNames.push(file.name)
|
||||
})
|
||||
let path
|
||||
if(isAdmin.value)
|
||||
path = props.currentPath
|
||||
else
|
||||
path = props.currentPath.substring(2)
|
||||
|
||||
axios.post(store.state.config.host + "share/?", qs.stringify({
|
||||
path, time: expireHour.value, count: count.value, fileNames,
|
||||
}, {indices: false})).then((res) => {
|
||||
if (res.data.result === 'success')
|
||||
ElMessage.success(res.data.data)
|
||||
else
|
||||
ElMessage.error(res.data.data)
|
||||
close()
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog title="分享文件" @close="close" v-model="isSharingFile">
|
||||
请输入分享时长或可下载ip个数(留空为该项不限制,都留空则永久分享):
|
||||
<el-container>
|
||||
<el-aside>
|
||||
已选择文件:<br>
|
||||
<span v-for="file in selectedFiles">
|
||||
{{file.name}}<br>
|
||||
</span>
|
||||
</el-aside>
|
||||
<el-main>
|
||||
<el-input v-model="expireHour">
|
||||
<template #prepend>
|
||||
分享时长
|
||||
</template>
|
||||
<template #append>
|
||||
小时
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input v-model="count">
|
||||
<template #prepend>
|
||||
下载ip个数
|
||||
</template>
|
||||
</el-input>
|
||||
</el-main>
|
||||
</el-container>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="shareFile">确定</el-button>
|
||||
<el-button @click="close">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
143
src/components/site-statue-card/index.vue
Normal file
143
src/components/site-statue-card/index.vue
Normal file
@ -0,0 +1,143 @@
|
||||
<script setup>
|
||||
import {onMounted, ref, watch} from "vue";
|
||||
import Util from "../../Util/index.js";
|
||||
import minimizeStr from '../minimize-str/index.vue'
|
||||
let props = defineProps(['status', 'site'])
|
||||
|
||||
let headerStyle = ref({})
|
||||
let cpuPercentage = ref(0)
|
||||
let memoryPercentage = ref(0)
|
||||
let usedMemory = ref(0)
|
||||
let totalMemory = ref(0)
|
||||
let loadPercentage = ref(0)
|
||||
let diskPercentage = ref(0)
|
||||
let usedSpace = ref(0)
|
||||
let totalSpace = ref(0)
|
||||
let bootTime = ref()
|
||||
let upTime = ref()
|
||||
let systemLoad = ref([0, 0, 0])
|
||||
let site = props.site
|
||||
let lastOnline = Util.formatDate(site.lastOnline)
|
||||
let ioRead = ref("0B/s")
|
||||
let ioWrite = ref("0B/s")
|
||||
let networkReceive = ref("0B/s")
|
||||
let networkSend = ref("0B/s")
|
||||
watch(props, () => {
|
||||
let status = props.status
|
||||
if(status !== null && status !== undefined) {
|
||||
cpuPercentage.value = status.usedCpuPercentage
|
||||
memoryPercentage.value = status.usedMemoryPercentage
|
||||
usedMemory.value = Util.formatSize(status.usedMemory)
|
||||
totalMemory.value = Util.formatSize(status.totalMemory)
|
||||
diskPercentage.value = status.usedSpacePercentage
|
||||
usedSpace.value = Util.formatSize(status.usedSpace)
|
||||
totalSpace.value = Util.formatSize(status.totalSpace)
|
||||
bootTime.value = new Date(status.systemBootTime * 1000).toLocaleString('zh-CN')
|
||||
upTime.value = Util.formatDuration(status.systemUpTime)
|
||||
systemLoad.value = status.systemLoad
|
||||
ioRead.value = Util.formatSize(status.ioRead) + "/s"
|
||||
ioWrite.value = Util.formatSize(status.ioWrite) + "/s"
|
||||
networkReceive.value = Util.formatSize(status.networkReceive) + "/s"
|
||||
networkSend.value = Util.formatSize(status.networkSend) + "/s"
|
||||
|
||||
if (systemLoad.value[0] !== -1)
|
||||
for (let i = 0; i < 3; i++)
|
||||
systemLoad.value[i] = Number(systemLoad.value[i]).toFixed(2)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if(site.online)
|
||||
headerStyle.value['--color'] = 'green'
|
||||
else
|
||||
headerStyle.value['--color'] = 'gray'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card :header-color="site.online?'green':'gray'" class="home-card">
|
||||
<div class="header">
|
||||
<span :style="headerStyle">{{`${site.id}:${site.hostname}`}}</span>
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col :span="12" style="text-align: center">
|
||||
<el-progress type="dashboard" :percentage="cpuPercentage" :color="Util.colors" style="text-align: center">
|
||||
<template #default>
|
||||
{{cpuPercentage}}% <br>
|
||||
CPU
|
||||
</template>
|
||||
</el-progress>
|
||||
<span style="text-align: center; display: block">{{(cpuPercentage * site.cpuCore * 0.01).toFixed(2)}} / {{site.cpuCore}}</span>
|
||||
</el-col>
|
||||
<el-col :span="12" style="text-align: center">
|
||||
<el-progress type="dashboard" :percentage="memoryPercentage" :color="Util.colors">
|
||||
<template #default>
|
||||
{{memoryPercentage}}% <br>
|
||||
内存
|
||||
</template>
|
||||
</el-progress>
|
||||
<span style="text-align: center; display: block">{{usedMemory}}/{{totalMemory}}</span>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row style="margin-top: 10px">
|
||||
<el-col :span="12" style="text-align: center">
|
||||
<el-progress type="dashboard" :percentage="loadPercentage" :color="Util.colors">
|
||||
<template #default>
|
||||
{{loadPercentage}}% <br>
|
||||
负载
|
||||
</template>
|
||||
</el-progress>
|
||||
<span style="text-align: center; display: block">{{systemLoad[0]}}/{{systemLoad[1]}}/{{systemLoad[2]}}</span>
|
||||
</el-col>
|
||||
<el-col :span="12" style="text-align: center;">
|
||||
<el-progress type="dashboard" :percentage="diskPercentage" :color="Util.colors">
|
||||
<template #default>
|
||||
{{diskPercentage}}% <br>
|
||||
硬盘
|
||||
</template>
|
||||
</el-progress>
|
||||
<span style="text-align: center; display: block">{{usedSpace}}/{{totalSpace}}</span>
|
||||
</el-col>
|
||||
<el-col>
|
||||
IP地址:{{site.ip}}<br>
|
||||
系统:<minimize-str :str="site.system" :length="25"/>
|
||||
<br>
|
||||
主机名:{{site.hostname}}<br>
|
||||
处理器架构:{{site.cpuArch}}<br>
|
||||
处理器名称:<minimize-str :str="site.cpuName" :length="25"/><br>
|
||||
处理器核心数:{{site.cpuCore}}<br>
|
||||
处理器线程数:{{site.cpuThread}}<br>
|
||||
<div v-if="site.online">
|
||||
运行时间:{{upTime}}<br>
|
||||
开机时间:{{bootTime}}<br>
|
||||
磁盘读写:{{ioRead + " " + ioWrite}}<br>
|
||||
网络收发:{{networkReceive + " " + networkSend}}<br>
|
||||
</div>
|
||||
<div v-else>
|
||||
最后上线时间:{{lastOnline}}<br>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.header > span {
|
||||
position: relative;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-left: 18px;
|
||||
}
|
||||
|
||||
.header > span::before {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: -13px;
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
content: '';
|
||||
background: var(--color);
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
153
src/components/view-download-record/index.vue
Normal file
153
src/components/view-download-record/index.vue
Normal file
@ -0,0 +1,153 @@
|
||||
<script setup>
|
||||
import Util from "../../Util/index.js";
|
||||
import {computed, ref, watch} from "vue";
|
||||
import minimizeStr from '../../components/minimize-str/index.vue'
|
||||
|
||||
let props = defineProps(['isViewDownloadRecord', 'shareFiles', 'viewType', 'currentFile', 'currentSite'])
|
||||
let emits = defineEmits(['close'])
|
||||
let isViewDownloadRecord = computed(() => {
|
||||
return props.isViewDownloadRecord
|
||||
})
|
||||
let computedDownloadRecord = computed(() => {
|
||||
let downloadRecord = []
|
||||
|
||||
if(props.viewType === 'single')
|
||||
downloadRecord.push(...props.currentFile.downloadRecords)
|
||||
else
|
||||
props.shareFiles.forEach((file) => {
|
||||
if(file.downloadRecords !== undefined)
|
||||
file.downloadRecords.forEach((record) => {
|
||||
downloadRecord.push({name:file.name, path:file.filePath, username:file.username, ...record})
|
||||
})
|
||||
})
|
||||
|
||||
return downloadRecord
|
||||
})
|
||||
let filterResult = ref([])
|
||||
let filterCondition = [
|
||||
{label:'筛选时间', value:'time'},
|
||||
{label:'筛选ip', value:'ip'},
|
||||
{label:'筛选ua', value:'ua'},
|
||||
]
|
||||
|
||||
let filterParam = ref('')
|
||||
let filterBy = ref('ip')
|
||||
|
||||
watch(props.viewType, () => {
|
||||
if(props.viewType === 'all') {
|
||||
if (filterCondition.length === 3)
|
||||
filterCondition.push({label: '筛选分享人', value: 'sharer'})
|
||||
} else if(filterCondition.length === 4)
|
||||
filterCondition.pop()
|
||||
})
|
||||
watch(filterParam, () => {
|
||||
filterResult.value.splice(0)
|
||||
switch (filterBy.value){
|
||||
case 'time':
|
||||
if(filterParam.value[0] !== undefined)
|
||||
computedDownloadRecord.value.forEach((record) => {
|
||||
if(filterParam.value[0].getTime() <= record.time && filterParam.value[1].getTime() >= record.time)
|
||||
filterResult.value.push(record)
|
||||
})
|
||||
break
|
||||
case 'ip':
|
||||
if(filterParam.value.trim() !== ''){
|
||||
computedDownloadRecord.value.forEach((record) => {
|
||||
if(record.ip.includes(filterParam.value))
|
||||
filterResult.value.push(record)
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'ua':
|
||||
if(filterParam.value.trim() !== ''){
|
||||
computedDownloadRecord.value.forEach((record) => {
|
||||
if(record.ua.includes(filterParam.value))
|
||||
filterResult.value.push(record)
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'sharer':
|
||||
if(filterParam.value.trim() !== ''){
|
||||
computedDownloadRecord.value.forEach((record) => {
|
||||
if(record.username.includes(filterParam.value))
|
||||
filterResult.value.push(record)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function handleSelectChange(){
|
||||
filterParam.value = ""
|
||||
}
|
||||
function close(){
|
||||
filterBy.value = 'ip'
|
||||
filterParam.value = ""
|
||||
emits('close')
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog title="查看下载记录" v-model="isViewDownloadRecord" :width="viewType==='single'?null:'1250px'" @close="close" top="0">
|
||||
<el-row>
|
||||
<el-col :span="10" v-if="viewType === 'single'">
|
||||
当前文件:{{currentFile.name}}
|
||||
</el-col>
|
||||
<el-col :span="10" v-if="viewType === 'all'">
|
||||
当前节点:{{currentSite.hostname}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-select placeholder="筛选" v-model="filterBy" @change="handleSelectChange">
|
||||
<el-option v-for="condition in filterCondition" :label="condition.label" :value="condition.value"/>
|
||||
</el-select>
|
||||
<div v-show="filterBy === 'time'" style="display: inline">
|
||||
<el-date-picker type="datetimerange" v-model="filterParam"/>
|
||||
</div>
|
||||
<el-input style="width: 300px" v-show="filterBy !== 'time'" v-model="filterParam">
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-table :data="filterParam.length>1?filterResult:computedDownloadRecord" empty-text="没有记录">
|
||||
<el-table-column label="文件" v-if="viewType === 'all'" width="400px">
|
||||
<template #default="scoped">
|
||||
<minimize-str :str="scoped.row.path" length="75"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分享人" v-if="viewType === 'all'" width="100px">
|
||||
<template #default="scoped">
|
||||
<minimize-str :str="scoped.row.username" length="10"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下载时间" width="200px">
|
||||
<template #default="scoped">
|
||||
{{Util.formatDate(scoped.row.time)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下载ip" width="200px">
|
||||
<template #default="scoped">
|
||||
{{scoped.row.ip}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下载ua" width="300px">
|
||||
<template #default="scoped">
|
||||
<el-popover width="300px">
|
||||
<template #reference>
|
||||
{{scoped.row.ua.length>75?scoped.row.ua.substring(0, 75) + '......':scoped.row.ua}}
|
||||
</template>
|
||||
<template #default>
|
||||
{{scoped.row.ua}}
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
13
src/main.js
Normal file
13
src/main.js
Normal file
@ -0,0 +1,13 @@
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import App from './App.vue'
|
||||
import element from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import {router} from "./router/index.js";
|
||||
import '@icon-park/vue-next/styles/index.css';
|
||||
|
||||
let app = createApp(App)
|
||||
|
||||
app.use(router)
|
||||
app.use(element)
|
||||
app.mount('#app')
|
||||
83
src/page/index.vue
Normal file
83
src/page/index.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<script setup>
|
||||
import SideBar from "../views/sideBar.vue";
|
||||
import store from '../store/index.js'
|
||||
import {ref, watch} from "vue";
|
||||
import {ElMessage} from "element-plus";
|
||||
import Util from "../Util/index.js";
|
||||
|
||||
let isPair = ref(false)
|
||||
let pairMessage = ref({})
|
||||
let size = ref(0)
|
||||
let sizeUnit = ref('GB')
|
||||
watch(store.getters.getPairMessage, () =>{
|
||||
pairMessage.value = store.getters.getPairMessage
|
||||
isPair.value = true
|
||||
})
|
||||
|
||||
function processPairRequest(result){
|
||||
let totalSpace
|
||||
if(result){
|
||||
if(isNaN(size.value) || size.value.trim() === ''){
|
||||
ElMessage.error("请输入正确的格式")
|
||||
return
|
||||
}
|
||||
totalSpace = size.value * Util[sizeUnit.value]
|
||||
if(totalSpace <= 0 || totalSpace > pairMessage.value.availableSpace){
|
||||
ElMessage.error("容量输入错误,不能为0或超出服务器可用容量")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
store.dispatch("processPairRequest", {result, totalSpace:totalSpace})
|
||||
isPair.value = false
|
||||
}
|
||||
|
||||
function logout(){
|
||||
localStorage.removeItem("passcode")
|
||||
delete store.state.config.passcode
|
||||
store.state.websocket.close()
|
||||
ElMessage({type: 'success', message: '退出登录成功'})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-container>
|
||||
<side-bar style="width: 12vw;"/>
|
||||
<el-main style="height: 95vh; width: 88vw; padding-top: 0; overflow: hidden">
|
||||
<el-scrollbar max-height="95vh">
|
||||
<router-view/>
|
||||
</el-scrollbar>
|
||||
</el-main>
|
||||
</el-container>
|
||||
<el-dialog v-model="isPair" title="配对请求">
|
||||
ip地址:{{pairMessage.ip}}<br>
|
||||
主机名:{{pairMessage.hostname}}<br>
|
||||
系统版本:{{pairMessage.system}}<br>
|
||||
cpu架构:{{pairMessage.cpuArch}}<br>
|
||||
cpu核心数:{{pairMessage.cpuCore}}<br>
|
||||
cpu线程数:{{pairMessage.cpuThread}}<br>
|
||||
反代前缀:{{pairMessage.reverseProxyPrefix === null ? '无': pairMessage.reverseProxyPrefix}}<br>
|
||||
域名:{{pairMessage.domain === null? '无': pairMessage.domain}}<br>
|
||||
可用空间:{{Util.formatSize(pairMessage.availableSpace)}}<br>
|
||||
<el-input style="width: 250px" v-model="size">
|
||||
<template #prepend>
|
||||
分配容量:
|
||||
</template>
|
||||
<template #append>
|
||||
<el-select placeholder="单位" v-model="sizeUnit">
|
||||
<el-option value="KB"/>
|
||||
<el-option value="MB"/>
|
||||
<el-option value="GB"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
<template #footer>
|
||||
<el-button @click="processPairRequest(true)" type="primary">允许</el-button>
|
||||
<el-button @click="processPairRequest(false)">拒绝</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
99
src/page/login.vue
Normal file
99
src/page/login.vue
Normal file
@ -0,0 +1,99 @@
|
||||
<script setup>
|
||||
import {onMounted, ref} from "vue";
|
||||
import axios from "axios";
|
||||
import {ElMessage} from "element-plus";
|
||||
import store from "../store/index.js";
|
||||
import {router} from "../router/index.js";
|
||||
|
||||
let username = ref()
|
||||
let passcode = ref()
|
||||
let rememberPasscode = ref(false)
|
||||
function login(){
|
||||
if(passcode.value.trim() === '' || username.value.trim() === ''){
|
||||
ElMessage.error('请输入用户名或密码')
|
||||
return
|
||||
}
|
||||
|
||||
axios.post(store.state.config.host + `login?username=${username.value}&passcode=${passcode.value}`).then((res) => {
|
||||
if(res.data.result === 'success') {
|
||||
store.state.config.username = username.value
|
||||
store.state.config.passcode = passcode.value
|
||||
store.state.config.isAdmin = res.data.isAdmin === 'true'
|
||||
store.state.config.sessionId = res.data.sessionId
|
||||
store.dispatch("loadSites")
|
||||
store.dispatch("initWebsocket")
|
||||
store.dispatch("loadUsers")
|
||||
|
||||
if(rememberPasscode.value) {
|
||||
localStorage.setItem("username", username.value)
|
||||
localStorage.setItem("passcode", passcode.value)
|
||||
}
|
||||
let lastTime = localStorage.getItem("lastTime")
|
||||
|
||||
ElMessage.success('登陆成功')
|
||||
if(lastTime !== null && lastTime.includes("?"))
|
||||
router.push(lastTime)
|
||||
else
|
||||
router.push('/index/status/?type=all')
|
||||
} else {
|
||||
ElMessage.error(res.data.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if(localStorage.getItem("logout") !== null){
|
||||
localStorage.removeItem("logout")
|
||||
} else if((localStorage.getItem("passcode")) !== null){
|
||||
username.value = localStorage.getItem("username")
|
||||
passcode.value = localStorage.getItem("passcode")
|
||||
login()
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
登录网盘<br>
|
||||
<el-form>
|
||||
<el-input v-model="username">
|
||||
<template #prepend>
|
||||
用户名
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input v-model="passcode" type="password">
|
||||
<template #prepend>
|
||||
密码
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form>
|
||||
<div>
|
||||
<el-checkbox v-model="rememberPasscode" style="float: left">是否记住密码</el-checkbox>
|
||||
<el-button @click="login(passcode)" style="text-align: right">登录</el-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
padding: 20px;
|
||||
width: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f0f0f0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
70
src/router/index.js
Normal file
70
src/router/index.js
Normal file
@ -0,0 +1,70 @@
|
||||
|
||||
import status from '../views/status/index.vue'
|
||||
import fileManage from '../views/file-manage/index.vue'
|
||||
import taskManage from '../views/task-manage/index.vue'
|
||||
import siteManage from '../views/site-manage/index.vue'
|
||||
import shareManage from '../views/share-manage/index.vue'
|
||||
import userManage from '../views/user-manage/index.vue'
|
||||
import { createRouter,createWebHashHistory} from "vue-router";
|
||||
import login from "../page/login.vue";
|
||||
import index from "../page/index.vue";
|
||||
import {watch} from "vue";
|
||||
import store from "../store/index.js";
|
||||
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/login',
|
||||
component: login
|
||||
},
|
||||
{
|
||||
path: '/index',
|
||||
component: index,
|
||||
children: [
|
||||
{
|
||||
path: 'status',
|
||||
component : status
|
||||
},
|
||||
{
|
||||
path: 'fileManage',
|
||||
component : fileManage
|
||||
},
|
||||
{
|
||||
path: 'taskManage',
|
||||
component : taskManage
|
||||
},
|
||||
{
|
||||
path: "siteManage",
|
||||
component: siteManage
|
||||
},
|
||||
{
|
||||
path: 'shareManage',
|
||||
component: shareManage
|
||||
},
|
||||
{
|
||||
path: "userManage",
|
||||
component: userManage
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
export const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
watch(() => store.state.isWsOnline, () => {
|
||||
if(!store.state.isWsOnline)
|
||||
router.push("/login")
|
||||
})
|
||||
router.beforeEach((to, from) => {
|
||||
if(to.path !== '/login') {
|
||||
if ('passcode' in store.state.config) {
|
||||
localStorage.setItem("lastTime", to.fullPath)
|
||||
return true
|
||||
} else
|
||||
return {path: '/login'}
|
||||
}
|
||||
})
|
||||
270
src/store/index.js
Normal file
270
src/store/index.js
Normal file
@ -0,0 +1,270 @@
|
||||
import vuex from 'vuex'
|
||||
import axios from "axios";
|
||||
import qs from "qs";
|
||||
import {ElMessage} from "element-plus";
|
||||
axios.defaults.withCredentials = true
|
||||
|
||||
const actions = {
|
||||
initWebsocket(context){
|
||||
state.websocket = new WebSocket("wss://sns.lionwebsite.xyz/ws/")
|
||||
state.websocket.onopen = () => {
|
||||
state.websocket.send(`{\"type\": \"init\", \"username\":\"${state.config.username}\", \"passcode\": \"${state.config.passcode}\"}`)
|
||||
context.commit("updateWsStatus", true)
|
||||
}
|
||||
state.websocket.onmessage = (event) => {
|
||||
let message = JSON.parse(event.data)
|
||||
|
||||
if(message.type !== undefined) {
|
||||
switch (message.type){
|
||||
case 'status':
|
||||
delete message.type
|
||||
context.commit("updateStatues", message)
|
||||
break
|
||||
case 'pair':
|
||||
context.commit("updatePairMessage", message.data)
|
||||
break
|
||||
case 'task':
|
||||
context.commit("updateTasks", message.tasks)
|
||||
break
|
||||
case 'statusAlter':
|
||||
if(message.status === 'online')
|
||||
ElMessage.success(`服务器${message.hostname}上线`)
|
||||
else
|
||||
ElMessage.error(`服务器${message.hostname}下线`)
|
||||
context.dispatch('loadSites')
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
state.websocket.onclose = (event) => {
|
||||
context.commit("updateWsStatus", false)
|
||||
context.commit("logout")
|
||||
ElMessage.error("断开连接")
|
||||
}
|
||||
},
|
||||
loadSites(context){
|
||||
axios.get(state.config.host + "getSites").then((res) => {
|
||||
if(res.data.result === 'success') {
|
||||
context.commit("updateSites", res.data.data)
|
||||
}
|
||||
})
|
||||
},
|
||||
processPairRequest(context, data){
|
||||
state.websocket.send(JSON.stringify({type:'pair', messageId:state.pairMessage.messageId, ...data}))
|
||||
context.commit("updatePairMessage", null)
|
||||
if(data.result){
|
||||
setTimeout(() => {
|
||||
context.dispatch("loadSites")
|
||||
}, 1000)
|
||||
|
||||
}
|
||||
},
|
||||
loadFiles(context, path){
|
||||
axios.get(state.config.host + "file/get?" + qs.stringify({path})).then((res) =>{
|
||||
if(res.data.result === 'success'){
|
||||
context.commit("updateFiles", {files:res.data.data, isRoot:(path.endsWith(":") || path.trim() === '')})
|
||||
}else{
|
||||
ElMessage({type:'error', message:'文件加载失败:' + res.data.data})
|
||||
}
|
||||
})
|
||||
},
|
||||
loadUsers(context){
|
||||
if(context.state.config.isAdmin)
|
||||
axios.get(state.config.host + "manage/user/").then((res) => {
|
||||
if(res.data.result === "success")
|
||||
context.commit("updateUsers", res.data.data)
|
||||
else
|
||||
context.commit("updateUsers", [])
|
||||
})
|
||||
else
|
||||
axios.get(state.config.host + "getSelf").then((res) => {
|
||||
if(res.data.result === "success")
|
||||
context.commit("updateUsers", [res.data.data])
|
||||
else
|
||||
context.commit("updateUsers", [])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
updateStatues(state, statues){
|
||||
for(let id in statues) {
|
||||
let status = statues[id]
|
||||
state.statues[status.id] = status
|
||||
}
|
||||
},
|
||||
updateSites(state, sites){
|
||||
state.sites.splice(0)
|
||||
state.sites.push(...sites)
|
||||
},
|
||||
updatePairMessage(state, pairMessage){
|
||||
if(pairMessage === null){
|
||||
state.pairMessage = {}
|
||||
}else{
|
||||
state.pairMessage[pairMessage.messageId] = pairMessage
|
||||
state.pairMessage = pairMessage
|
||||
}
|
||||
},
|
||||
updateWsStatus(state, isOnline){
|
||||
state.isWsOnline = isOnline
|
||||
},
|
||||
updateFiles(state, data){
|
||||
state.files.splice(0)
|
||||
let {files, isRoot} = data
|
||||
let date = new Date()
|
||||
if(files.length === 0) {
|
||||
if (!isRoot)
|
||||
state.files.push({name: '...', type: 'FOLDER', size: 0, lastModify: date.getTime()})
|
||||
return
|
||||
}
|
||||
if(!isRoot)
|
||||
state.files.push({name:'...', type:'FOLDER', size:0, lastModify:date.getTime()})
|
||||
state.files.push(...files)
|
||||
state.files.sort((pre, next) => {
|
||||
if(pre.type === next.type)
|
||||
return 0
|
||||
else if(pre.type === "FOLDER")
|
||||
return -1
|
||||
else
|
||||
return 1
|
||||
})
|
||||
},
|
||||
removeTasks(state, taskIds){
|
||||
let tasks = state.tasks
|
||||
state.tasks.splice(0)
|
||||
tasks.forEach((task) => {
|
||||
let isRemove
|
||||
//去除命中的id
|
||||
taskIds = taskIds.filter((id) => {
|
||||
if(task.taskId === id) {
|
||||
isRemove = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
if(!isRemove)
|
||||
state.tasks.push(task)
|
||||
})
|
||||
state.deleteTaskIds.push(...taskIds)
|
||||
},
|
||||
updateTasks(state, tasks){
|
||||
state.tasks.splice(0)
|
||||
for(let id in tasks){
|
||||
let task = tasks[id]
|
||||
|
||||
//过滤待删除的id
|
||||
if(state.deleteTaskIds.length !== 0){
|
||||
let skip = false
|
||||
state.deleteTaskIds.forEach((id) => {
|
||||
skip = task.id === id
|
||||
})
|
||||
//只用一次
|
||||
state.deleteTaskIds.splice(0)
|
||||
if(skip)
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (task.type){
|
||||
case "compress":
|
||||
task.type = '打包'
|
||||
break
|
||||
case 'extract':
|
||||
task.type = '解包'
|
||||
break
|
||||
case 'transfer':
|
||||
if(state.config.isAdmin)
|
||||
task.type = '转移'
|
||||
else task.type = '导入'
|
||||
break
|
||||
}
|
||||
|
||||
switch (task.status){
|
||||
case "waiting":
|
||||
task.status = '等待中'
|
||||
break
|
||||
case "proceeding":
|
||||
task.status = '进行中'
|
||||
break
|
||||
case "success":
|
||||
task.status = '已完成'
|
||||
break
|
||||
case "failure":
|
||||
task.status = '任务失败'
|
||||
break
|
||||
}
|
||||
task.percentage = Number(Number(task.percentage).toFixed(2))
|
||||
|
||||
state.tasks.push(tasks[id])
|
||||
}
|
||||
},
|
||||
updatePasscode(state, passcode){
|
||||
state.config.passcode = passcode
|
||||
localStorage.setItem('passcode', passcode)
|
||||
},
|
||||
updateUsers(state, users){
|
||||
state.users.splice(0)
|
||||
state.users.push(...users)
|
||||
state.users.push({username:"新建用户", id:-1})
|
||||
},
|
||||
logout(state){
|
||||
state.sites.splice(0)
|
||||
state.files.splice(0)
|
||||
state.users.splice(0)
|
||||
state.tasks.splice(0)
|
||||
localStorage.setItem("logout", "logout")
|
||||
}
|
||||
}
|
||||
|
||||
const state = {
|
||||
statues: {},
|
||||
websocket: {},
|
||||
sites:[],
|
||||
files:[],
|
||||
users:[],
|
||||
config:{'host': "https://sns.lionwebsite.xyz/"},
|
||||
pairMessage:{},
|
||||
tasks:[],
|
||||
deleteTaskIds:[], //用于清除的任务id,有时候取消任务时,可能任务取消了,但是又发送了一份旧的任务,导致这份旧的任务一直显示在前台
|
||||
isWsOnline:false
|
||||
}
|
||||
|
||||
const getters = {
|
||||
getStatues(state){
|
||||
return state.statues
|
||||
},
|
||||
getSites(state){
|
||||
return state.sites
|
||||
},
|
||||
getOnlineSites(state){
|
||||
let sites = []
|
||||
state.sites.forEach((site) => {
|
||||
if(site.online)
|
||||
sites.push(site)
|
||||
})
|
||||
return sites
|
||||
},
|
||||
getOfflineSites(state){
|
||||
let sites = []
|
||||
state.sites.forEach((site) => {
|
||||
if(!site.online)
|
||||
sites.push(site)
|
||||
})
|
||||
return sites
|
||||
},
|
||||
getTasks(state){
|
||||
return state.tasks
|
||||
},
|
||||
getPairMessage(state){
|
||||
return state.pairMessage
|
||||
},
|
||||
getFiles(state){
|
||||
return state.files
|
||||
}
|
||||
}
|
||||
|
||||
export default new vuex.Store({
|
||||
actions,
|
||||
mutations,
|
||||
state,
|
||||
getters
|
||||
})
|
||||
547
src/views/file-manage/index.vue
Normal file
547
src/views/file-manage/index.vue
Normal file
@ -0,0 +1,547 @@
|
||||
<script setup>
|
||||
|
||||
import {computed, onMounted, ref, watch} from "vue";
|
||||
import store from "../../store/index.js";
|
||||
import {useRouter} from "vue-router";
|
||||
import {Folder, Share, UploadFilled, Files as FilesIcon} from "@element-plus/icons-vue";
|
||||
import Util from "../../Util";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import axios from "axios";
|
||||
import qs from "qs";
|
||||
import fileOperation from '../../components/file-operation/index.vue'
|
||||
import shareFile from '../../components/share-file/index.vue'
|
||||
import adjustShare from '../../components/adjust-share/index.vue'
|
||||
import importFile from '../../components/import-file/index.vue'
|
||||
|
||||
let router = useRouter()
|
||||
let onlineSites = computed(() => {
|
||||
return store.getters.getOnlineSites
|
||||
})
|
||||
let files = computed(() => {
|
||||
return store.getters.getFiles
|
||||
})
|
||||
let users = computed(() => {
|
||||
return store.state.users
|
||||
})
|
||||
|
||||
let currentPath = computed(() => {
|
||||
let currentPath = currentSiteId.value + ":"
|
||||
cursor.value.forEach((path) => {
|
||||
currentPath += path + "/"
|
||||
})
|
||||
return currentPath
|
||||
})
|
||||
let fileOperate = computed(() => {
|
||||
if(isMoveFile.value)
|
||||
return "move"
|
||||
else if(isCopyFile.value)
|
||||
return "copy"
|
||||
else
|
||||
return "extract"
|
||||
})
|
||||
let isMoveFile = ref(false)
|
||||
let isCopyFile = ref(false)
|
||||
let isExtractFile = ref(false)
|
||||
let isShareFile = ref(false)
|
||||
let isAdjustShare = ref(false)
|
||||
let isImportFile = ref(false)
|
||||
let isAdmin = computed(() => {
|
||||
return store.state.config.isAdmin
|
||||
})
|
||||
|
||||
let currentSiteId = ref(0)
|
||||
let cursor = ref([])
|
||||
let currentFile = ref()
|
||||
let selectNone = ref(true)
|
||||
let selectMultiFolder = ref(false)
|
||||
let selectMultiFile = ref(false)
|
||||
let selectSingleFile = ref(false)
|
||||
let selectSingleFolder = ref(false)
|
||||
let selectShared = ref(false)
|
||||
let selectTarFile = ref(false)
|
||||
let selectedFiles = []
|
||||
|
||||
|
||||
let isUpload = ref(false)
|
||||
function loadFiles(path){
|
||||
store.dispatch('loadFiles', path)
|
||||
}
|
||||
function uploadFile(){
|
||||
isUpload.value = true
|
||||
}
|
||||
function onUploadSuccess(res){
|
||||
if(res.result === "success") {
|
||||
ElMessage.success("文件上传成功")
|
||||
if(!isAdmin.value)
|
||||
axios.post(store.state.config.host + "verifySpace").then(() => {
|
||||
store.dispatch('loadUsers')
|
||||
})
|
||||
} else
|
||||
ElMessage.error(res.data)
|
||||
}
|
||||
|
||||
function loadFilesByCursor(){
|
||||
let path = ""
|
||||
if(isAdmin.value)
|
||||
path = currentSiteId.value + ":"
|
||||
if(cursor.value.length !== 0)
|
||||
cursor.value.forEach((str) => {
|
||||
if(!path.endsWith(":"))
|
||||
path += '/' + str
|
||||
else
|
||||
path += str
|
||||
})
|
||||
loadFiles(path)
|
||||
}
|
||||
function getSiteBySiteId(id){
|
||||
let getSite
|
||||
onlineSites.value.forEach((site) => {
|
||||
if(site.id === id)
|
||||
getSite = site
|
||||
})
|
||||
return getSite
|
||||
}
|
||||
let debounce = 0
|
||||
function handleClick(file){
|
||||
if(file.type === 'FOLDER'){
|
||||
clearTimeout(debounce)
|
||||
debounce = setTimeout(() => {
|
||||
if(file.name === '...')
|
||||
cursor.value.pop()
|
||||
else
|
||||
cursor.value.push(file.name)
|
||||
loadFilesByCursor()
|
||||
refreshParamsPath()
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
function refreshParamsPath(){
|
||||
let query = JSON.parse(JSON.stringify(router.currentRoute.value.query))
|
||||
query.path = currentPath.value
|
||||
router.push({path: router.currentRoute.value.path, query})
|
||||
}
|
||||
function deleteFiles() {
|
||||
let files = ''
|
||||
selectedFiles.forEach((file) => {
|
||||
files += " " + file.name
|
||||
})
|
||||
ElMessageBox.confirm(
|
||||
'以下文件将被删除: ' + files + " 是否确认删除?",
|
||||
'删除文件',
|
||||
{confirmButtonText: '删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'}
|
||||
).then(() => {
|
||||
deleteFilesConfirm()
|
||||
}).catch(() => {})
|
||||
|
||||
}
|
||||
function deleteFilesConfirm(){
|
||||
let paths = []
|
||||
selectedFiles.forEach((file) => {
|
||||
paths.push(file.path)
|
||||
})
|
||||
axios.post(store.state.config.host + "file/delete", qs.stringify({sourceId:currentSiteId.value, paths},{indices:false}))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success'){
|
||||
ElMessage({type:'success', message:res.data.data})
|
||||
loadFilesByCursor()
|
||||
refresh()
|
||||
}else{
|
||||
ElMessage({type: 'error', message:res.data.data})
|
||||
}
|
||||
})
|
||||
}
|
||||
function compressFiles(){
|
||||
let files = ''
|
||||
selectedFiles.forEach((file) => {
|
||||
files += " " + file.name
|
||||
})
|
||||
ElMessageBox.prompt(
|
||||
`以下文件即将被打包:${files},请输入压缩包名称`,
|
||||
'打包文件',
|
||||
{
|
||||
confirmButtonText: '打包',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info',
|
||||
inputValue: '.tar'
|
||||
}
|
||||
).then((packageName) => {
|
||||
if(packageName.value.trim() === ''){
|
||||
ElMessage.error("压缩包名不能为空")
|
||||
return
|
||||
}
|
||||
let paths = []
|
||||
selectedFiles.forEach((file) => {
|
||||
paths.push(file.path)
|
||||
})
|
||||
let targetPath = ""
|
||||
if(isAdmin.value)
|
||||
targetPath = currentSiteId.value + ":"
|
||||
cursor.value.push(packageName.value)
|
||||
cursor.value.forEach((path) => {
|
||||
if(targetPath.endsWith(":") || targetPath === "")
|
||||
targetPath += path
|
||||
else
|
||||
targetPath += "/" + path
|
||||
})
|
||||
cursor.value.pop()
|
||||
axios.post(store.state.config.host + "file/compress", qs.stringify({
|
||||
paths:paths, targetPath:targetPath}, {indices: false})).then((res) => {
|
||||
if(res.data.result === 'success'){
|
||||
ElMessage({type: 'success', message:'打包任务提交成功'})
|
||||
}else
|
||||
ElMessage({type: 'error', message: '打包任务提交失败:' + res.data.data})
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
function createFolder(){
|
||||
ElMessageBox.prompt("文件夹名称", '新建文件夹', {
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
inputValue:''
|
||||
}).then((newFolder) => {
|
||||
if(newFolder.value.trim() === ''){
|
||||
ElMessage.error("新文件夹名不能为空")
|
||||
return
|
||||
}
|
||||
let path = ""
|
||||
if(isAdmin.value)
|
||||
path = currentSiteId.value + ":"
|
||||
cursor.value.forEach((str) => {
|
||||
path += '/' + str
|
||||
})
|
||||
path += '/' + newFolder.value
|
||||
axios.post(store.state.config.host + "file/create?" +
|
||||
qs.stringify({path:path}))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success') {
|
||||
ElMessage({type: 'success', message: '文件夹创建成功'})
|
||||
loadFilesByCursor()
|
||||
}else {
|
||||
ElMessage({type: 'error', message: '文件夹创建失败:' + res.data.data})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
function rename(){
|
||||
ElMessageBox.prompt(
|
||||
`即将重命名:${selectedFiles[0].name},请输入新文件名`,
|
||||
'文件重命名',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info',
|
||||
inputValue:selectedFiles[0].name,
|
||||
}
|
||||
).then((name) => {
|
||||
if(name.value.trim() === ''){
|
||||
ElMessage.error("新文件名不能为空")
|
||||
return
|
||||
}
|
||||
let path = selectedFiles[0].path
|
||||
if(isAdmin.value)
|
||||
path = currentSiteId.value + ":" + path
|
||||
axios.post(store.state.config.host + "file/rename", qs.stringify({
|
||||
path:path, name:name.value}, {indices: false})).then((res) => {
|
||||
if(res.data.result === 'success'){
|
||||
ElMessage({type: 'success', message:'重命名成功'})
|
||||
loadFilesByCursor()
|
||||
}else
|
||||
ElMessage({type: 'error', message: '重命名失败:' + res.data.data})
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
function jump(index){
|
||||
cursor.value.splice(index)
|
||||
loadFilesByCursor()
|
||||
refreshParamsPath()
|
||||
}
|
||||
function switchSite(id){
|
||||
currentSiteId.value = id
|
||||
cursor.value.splice(0)
|
||||
loadFilesByCursor()
|
||||
refreshParamsPath()
|
||||
}
|
||||
function selectChange(files){
|
||||
selectedFiles = files
|
||||
selectShared.value = false
|
||||
selectNone.value = true
|
||||
selectSingleFile.value = false
|
||||
selectSingleFolder.value = false
|
||||
selectMultiFile.value = false
|
||||
selectMultiFolder.value = false
|
||||
selectTarFile.value = false
|
||||
let selectedFile = 0
|
||||
let selectedFolder = 0
|
||||
files.forEach((file) => {
|
||||
if(file.type === 'FOLDER')
|
||||
selectedFolder++
|
||||
else {
|
||||
if (file.shareCode !== undefined)
|
||||
selectShared.value = true
|
||||
if(!selectTarFile.value && file.name.endsWith(".tar"))
|
||||
selectTarFile.value = true
|
||||
selectedFile++
|
||||
}
|
||||
})
|
||||
|
||||
selectNone.value = selectedFile + selectedFolder === 0;
|
||||
|
||||
if(selectedFile === 1)
|
||||
selectSingleFile.value = true
|
||||
else if(selectedFile > 1)
|
||||
selectMultiFile.value = true
|
||||
|
||||
|
||||
if(selectedFolder === 1)
|
||||
selectSingleFolder.value = true
|
||||
else if(selectedFolder > 1)
|
||||
selectMultiFolder.value = true
|
||||
}
|
||||
|
||||
function extractFiles(){
|
||||
isExtractFile.value = true
|
||||
}
|
||||
function moveFile(){
|
||||
isMoveFile.value = true
|
||||
}
|
||||
function copyFile() {
|
||||
isCopyFile.value = true
|
||||
}
|
||||
|
||||
function closeFileOperation(){
|
||||
isCopyFile.value = false
|
||||
isMoveFile.value = false
|
||||
isExtractFile.value = false
|
||||
setTimeout(() => {
|
||||
loadFilesByCursor()
|
||||
}, 500)
|
||||
}
|
||||
function closeShareFile(){
|
||||
isShareFile.value = false
|
||||
setTimeout(() => {
|
||||
loadFilesByCursor()
|
||||
}, 500)
|
||||
}
|
||||
function closeAdjustShare(){
|
||||
isAdjustShare.value = false
|
||||
setTimeout(() => {
|
||||
loadFilesByCursor()
|
||||
}, 500)
|
||||
}
|
||||
function cancelShare(shareCode){
|
||||
if(isAdmin.value)
|
||||
shareCode = currentSiteId.value + ":" + shareCode
|
||||
axios.post(store.state.config.host + "share/cancel?", qs.stringify({shareCode}))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success')
|
||||
ElMessage.success(res.data.data)
|
||||
else
|
||||
ElMessage.error(res.data.data)
|
||||
setTimeout(() => {
|
||||
loadFilesByCursor()
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
function adjustShareFile(file) {
|
||||
isAdjustShare.value = true
|
||||
currentFile.value = file
|
||||
}
|
||||
|
||||
function downloadFile(file){
|
||||
if(file.type === 'FILE')
|
||||
ElMessageBox.confirm(`是否下载文件:${file.name}?`, '下载文件',
|
||||
{confirmButtonText:'确认', cancelButtonText:'取消'}).then(() => {
|
||||
window.open(getSiteBySiteId(currentSiteId.value).host + `file/getFile/${file.name}?` + qs.stringify({path:currentPath.value.substring(currentPath.value.indexOf(":") + 1) + file.name, sessionId: store.state.config.sessionId}))
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if(router.currentRoute.value.query.path === undefined){
|
||||
loadDefaultFiles()
|
||||
}else {
|
||||
let path = router.currentRoute.value.query.path
|
||||
if(!path.includes(":")){
|
||||
ElMessage.error("路径错误")
|
||||
loadDefaultFiles()
|
||||
return
|
||||
}
|
||||
initFileCursor(path)
|
||||
loadFilesByCursor()
|
||||
}
|
||||
})
|
||||
|
||||
function initFileCursor(path){
|
||||
cursor.value.splice(0)
|
||||
let temp = path.split(":")
|
||||
currentSiteId.value = Number(temp[0])
|
||||
path = temp[1]
|
||||
|
||||
if(path === undefined){
|
||||
loadFilesByCursor()
|
||||
return
|
||||
}
|
||||
if(path.includes("/")) {
|
||||
path.split("/").forEach((t) => {
|
||||
if(t.trim() !== '')
|
||||
cursor.value.push(t)
|
||||
})
|
||||
}else if(path.trim() !== '')
|
||||
cursor.value.push(path)
|
||||
}
|
||||
function loadDefaultFiles() {
|
||||
router.push(`/index/fileManage/?path=${onlineSites.value[0].id}:`)
|
||||
currentSiteId.value = onlineSites.value[0].id
|
||||
loadFilesByCursor()
|
||||
}
|
||||
function refresh(){
|
||||
if(isAdmin.value)
|
||||
store.dispatch('loadSites')
|
||||
else {
|
||||
axios.post(store.state.config.host + "verifySpace").then(() => {
|
||||
store.dispatch('loadUsers')
|
||||
})
|
||||
}
|
||||
loadFilesByCursor()
|
||||
}
|
||||
|
||||
//从其他组件切到文件组件的加载
|
||||
watch(router.currentRoute, (now, old) =>{
|
||||
if(now.path !== old.path && now.path === '/index/fileManage') {
|
||||
if (now.query.path === undefined) {
|
||||
router.replace(`/index/fileManage/?path=${onlineSites.value[0].id}:`)
|
||||
initFileCursor(`${onlineSites.value[0].id}:`)
|
||||
} else
|
||||
initFileCursor(now.query.path)
|
||||
|
||||
loadFilesByCursor()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-menu mode="horizontal" :default-active="currentSiteId + ''">
|
||||
<el-menu-item v-for="site in onlineSites" :index="site.id" @click="switchSite(site.id)">
|
||||
{{site.hostname}}
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
<div>
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="uploadFile">上传文件</el-button>
|
||||
<el-button type="primary" @click="isImportFile = true" v-if="!isAdmin">导入文件</el-button>
|
||||
<el-button @click="createFolder">新建文件夹</el-button>
|
||||
<el-button :disabled="selectNone || selectSingleFile" @click="compressFiles">打包</el-button>
|
||||
<el-button :disabled="!(selectSingleFile && selectTarFile)" @click="extractFiles">解包</el-button>
|
||||
<el-button :disabled="selectNone" @click="moveFile">移动到</el-button>
|
||||
<el-button :disabled="selectNone" @click="copyFile">复制到</el-button>
|
||||
<el-button :disabled="!(selectSingleFolder || selectSingleFile)" @click="rename">重命名</el-button>
|
||||
<el-button :disabled="selectNone" @click="deleteFiles">删除</el-button>
|
||||
<el-button :disabled="selectNone || selectMultiFolder || selectSingleFolder || selectShared" @click="isShareFile = true">创建分享链接</el-button>
|
||||
<el-button @click="refresh">刷新</el-button>
|
||||
</el-button-group>
|
||||
|
||||
<el-progress :percentage="((getSiteBySiteId(currentSiteId).totalSpace - getSiteBySiteId(currentSiteId).availableSpace) / getSiteBySiteId(currentSiteId).totalSpace) * 100"
|
||||
style="width: 150px; display: inline-block; float: right" v-if="isAdmin && getSiteBySiteId(currentSiteId) !== undefined" :color="Util.colors"
|
||||
>
|
||||
<template #default>
|
||||
{{Util.formatSize(getSiteBySiteId(currentSiteId).totalSpace - getSiteBySiteId(currentSiteId).availableSpace)}} / {{Util.formatSize(getSiteBySiteId(currentSiteId).totalSpace)}}
|
||||
</template>
|
||||
</el-progress>
|
||||
<el-progress :percentage=" ((users[0].totalSpace - users[0].availableSpace) / users[0].totalSpace) * 100"
|
||||
style="width: 150px; display: inline-block; float: right" v-if="!isAdmin && users[0] !== undefined"
|
||||
color="Util.colors">
|
||||
<template #default>
|
||||
{{Util.formatSize(users[0].totalSpace - users[0].availableSpace) }} / {{ Util.formatSize(users[0].totalSpace)}}
|
||||
</template>
|
||||
</el-progress>
|
||||
<br>
|
||||
<el-link :underline="false" @click="jump(0)" :disabled="cursor.length === 0">全部文件</el-link>
|
||||
<div v-for="(path, index) in cursor" style="display: inline-block">
|
||||
>
|
||||
<el-link :underline="false" :disabled="index === cursor.length - 1" @click="jump(index + 1)">{{path}}</el-link>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<el-table :data="files" size="large" @rowClick="handleClick" @selectionChange="selectChange"
|
||||
max-height="76vh" @rowDblclick="downloadFile" empty-text="根目录下没有文件">
|
||||
<el-table-column type="selection" width="55px"></el-table-column>
|
||||
<el-table-column width="50px">
|
||||
<template #default="scoped">
|
||||
<el-icon v-show="scoped.row.type === 'FOLDER'">
|
||||
<Folder />
|
||||
</el-icon>
|
||||
<el-icon v-show="scoped.row.type === 'FILE'">
|
||||
<FilesIcon/>
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="50px">
|
||||
<template #default="scoped">
|
||||
<el-popover v-if="scoped.row.shareCode !== undefined" width="250px">
|
||||
<template #reference>
|
||||
<el-icon >
|
||||
<Share />
|
||||
</el-icon>
|
||||
</template>
|
||||
<template #default>
|
||||
分享码:{{currentSiteId + ":" +scoped.row.shareCode}}<br>
|
||||
过期时间:{{scoped.row.expireTime === undefined?'不过期':Util.formatDate(scoped.row.expireTime)}}<br>
|
||||
剩余ip数:{{scoped.row.totalCount === undefined?'不限制':scoped.row.availableCount ?? 0}}<br>
|
||||
下载链接:<a :href="getSiteBySiteId(currentSiteId).host + `getFileByShareCode/${scoped.row.name}?shareCode=${scoped.row.shareCode}`">链接</a><br>
|
||||
<el-button type="danger" @click="cancelShare(scoped.row.shareCode)">取消分享</el-button>
|
||||
<el-button @click="adjustShareFile(scoped.row)">调整分享</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件名" width="500px">
|
||||
<template #default="scoped">
|
||||
{{scoped.row.name}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="大小" width="100px">
|
||||
<template #default="scoped">
|
||||
<span v-if="scoped.row.type === 'FILE'">
|
||||
{{Util.formatSize(scoped.row.size)}}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="修改时间">
|
||||
<template #default="scoped">
|
||||
{{Util.formatDate(scoped.row.lastModify)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-dialog title="上传文件" v-model="isUpload" @close="loadFilesByCursor">
|
||||
<el-upload drag :action="getSiteBySiteId(currentSiteId).host + `file/upload/${currentPath.substring(currentPath.indexOf(':') + 1)}?sessionId=${store.state.config.sessionId}`" multiple
|
||||
:on-success="onUploadSuccess">
|
||||
<el-icon class="el-icon--upload"><upload-filled/></el-icon>
|
||||
<div class="el-upload__text">
|
||||
拖拽文件到这里或 <em>点击上传</em> 上传至 {{getSiteBySiteId(currentSiteId).hostname}}
|
||||
<span v-for="(path) in cursor" > > {{path}}</span>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
上传限制大小:10Gb
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-dialog>
|
||||
|
||||
<file-operation :selected-files="selectedFiles" :is-operating-file="isCopyFile || isMoveFile || isExtractFile"
|
||||
:cross-site="!(selectSingleFolder || selectMultiFolder || isExtractFile)" @close="closeFileOperation" :current-side-id="currentSiteId"
|
||||
:operation="fileOperate" :current-path="currentPath"/>
|
||||
|
||||
<share-file :is-sharing-file="isShareFile" :selected-files="selectedFiles" :current-path="currentPath" @close="closeShareFile"/>
|
||||
<adjust-share :current-file="currentFile" :is-adjusting-share="isAdjustShare" :current-site-id="currentSiteId" @close="closeAdjustShare"/>
|
||||
<import-file :is-import-file="isImportFile" :path="currentPath" @close="() => {isImportFile = false; refresh}"/>
|
||||
</template>
|
||||
<style scoped>
|
||||
</style>
|
||||
163
src/views/share-manage/index.vue
Normal file
163
src/views/share-manage/index.vue
Normal file
@ -0,0 +1,163 @@
|
||||
<script setup>
|
||||
import {computed, onMounted, ref, watch} from "vue";
|
||||
import store from "../../store/index.js";
|
||||
import {useRouter} from "vue-router";
|
||||
import {ElMessage} from "element-plus";
|
||||
import axios from "axios";
|
||||
import qs from "qs";
|
||||
import Util from "../../Util/index.js";
|
||||
import AdjustShare from '../../components/adjust-share/index.vue'
|
||||
import ViewDownloadRecord from '../../components/view-download-record/index.vue'
|
||||
import MinimizeStr from '../../components/minimize-str/index.vue'
|
||||
|
||||
let router = useRouter()
|
||||
let onlineSites = computed(() => {
|
||||
return store.getters.getOnlineSites
|
||||
})
|
||||
let currentSiteId = ref()
|
||||
let shareFiles = ref([])
|
||||
|
||||
let isAdjustShare = ref(false)
|
||||
let isViewDownloadRecord = ref(false)
|
||||
let viewType = ref('') //all single
|
||||
let currentFile = ref()
|
||||
|
||||
function getSiteBySiteId(id){
|
||||
let getSite
|
||||
onlineSites.value.forEach((site) => {
|
||||
if(site.id === id)
|
||||
getSite = site
|
||||
})
|
||||
return getSite
|
||||
}
|
||||
|
||||
function switchSite(id){
|
||||
currentSiteId.value = id
|
||||
router.replace("/index/shareManage/?id=" + currentSiteId.value)
|
||||
loadShareFiles()
|
||||
}
|
||||
|
||||
function viewSingleDownloadRecord(file) {
|
||||
currentFile.value = file
|
||||
isViewDownloadRecord.value = true
|
||||
viewType.value = 'single'
|
||||
}
|
||||
|
||||
function viewAllDownloadRecord(){
|
||||
isViewDownloadRecord.value = true
|
||||
viewType.value = 'all'
|
||||
}
|
||||
|
||||
function loadShareFiles(){
|
||||
axios.post(store.state.config.host + "share/get?", qs.stringify({sourceId:currentSiteId.value})).
|
||||
then((res) => {
|
||||
shareFiles.value.splice(0)
|
||||
if(res.data.result === 'success') {
|
||||
res.data.data.forEach((shareFile) => {
|
||||
shareFile.name = shareFile.filePath.substring(shareFile.filePath.lastIndexOf("/")+1)
|
||||
shareFiles.value.push(shareFile)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
function download(file) {
|
||||
window.open(getSiteBySiteId(currentSiteId.value).host + `file/getFile/${file.name}?` + qs.stringify({path:file.filePath.substring(1)}))
|
||||
}
|
||||
function copyShareLink(file){
|
||||
let link = getSiteBySiteId(currentSiteId.value).host + `getFileByShareCode/${file.name}?shareCode=${file.shareCode}`
|
||||
navigator.clipboard.writeText(link)
|
||||
ElMessage.success("复制成功")
|
||||
}
|
||||
function adjustShareFile(file){
|
||||
if(file.filePath.includes("\\"))
|
||||
file.name = file.filePath.substring(file.filePath.lastIndexOf("\\")+1)
|
||||
else
|
||||
file.name = file.filePath.substring(file.filePath.lastIndexOf("/")+1)
|
||||
currentFile.value = file
|
||||
isAdjustShare.value = true
|
||||
}
|
||||
function closeAdjustShare(){
|
||||
isAdjustShare.value = false
|
||||
loadShareFiles()
|
||||
}
|
||||
function cancelShare(shareCode){
|
||||
axios.post(store.state.config.host + "share/cancel?", qs.stringify({shareCode:currentSiteId.value + ":" + shareCode}))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success')
|
||||
ElMessage.success(res.data.data)
|
||||
else
|
||||
ElMessage.error(res.data.data)
|
||||
loadShareFiles()
|
||||
})
|
||||
}
|
||||
watch(router.currentRoute, (old, now)=>{
|
||||
if(old.path !== now.path && now.path === '/index/shareManage/')
|
||||
if(now.query.id === undefined) {
|
||||
router.replace(`/index/shareManage/?id=${onlineSites.value[0].id}`)
|
||||
currentSiteId.value = onlineSites.value[0].id
|
||||
loadShareFiles()
|
||||
}
|
||||
})
|
||||
onMounted(() => {
|
||||
if(router.currentRoute.value.query.id === undefined) {
|
||||
router.replace(`/index/shareManage/?id=${onlineSites.value[0].id}`)
|
||||
currentSiteId.value = onlineSites.value[0].id
|
||||
}else
|
||||
currentSiteId.value = Number(router.currentRoute.value.query.id)
|
||||
loadShareFiles()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-menu mode="horizontal" :default-active="currentSiteId">
|
||||
<el-menu-item v-for="site in onlineSites" :index="site.id" @click="switchSite(site.id)" >
|
||||
{{site.hostname}}
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
<el-button @click="viewAllDownloadRecord" v-if="store.state.config.isAdmin">查看当前节点下载记录</el-button>
|
||||
<el-table :data="shareFiles" empty-text="此节点未分享文件">
|
||||
<el-table-column label="路径" width="350px">
|
||||
<template #default="scoped">
|
||||
<minimize-str :str="scoped.row.filePath" :length="50"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分享人" v-if="store.state.config.isAdmin" width="100px">
|
||||
<template #default="scoped">
|
||||
<minimize-str :str="scoped.row.username" :length="10"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分享码" width="90px">
|
||||
<template #default="scoped">
|
||||
{{scoped.row.shareCode}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="过期时间" width="150px">
|
||||
<template #default="scoped">
|
||||
{{scoped.row.expireTime === undefined?"不过期":Util.formatDate(scoped.row.expireTime)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剩余ip数" width="100px">
|
||||
<template #default="scoped">
|
||||
{{scoped.row.availableCount === undefined?"不限制":scoped.row.availableCount}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template #default="scoped">
|
||||
<el-button type="primary" @click="download(scoped.row)">下载</el-button>
|
||||
<el-button type="primary" @click="adjustShareFile(scoped.row)">调整</el-button>
|
||||
<el-button type="danger" @click="cancelShare(scoped.row.shareCode)">取消</el-button>
|
||||
<el-button @click="copyShareLink(scoped.row)">复制链接</el-button>
|
||||
<el-button v-if="scoped.row.downloadRecords !== undefined" @click="viewSingleDownloadRecord(scoped.row)">查看下载记录</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<adjust-share :current-site-id="currentSiteId" :is-adjusting-share="isAdjustShare"
|
||||
:current-file="currentFile" @close="closeAdjustShare"/>
|
||||
|
||||
<view-download-record :current-file="currentFile" :is-view-download-record="isViewDownloadRecord" :share-files="shareFiles" :view-type="viewType" :current-site="getSiteBySiteId(currentSiteId)" @close="isViewDownloadRecord = false"/>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
97
src/views/sideBar.vue
Normal file
97
src/views/sideBar.vue
Normal file
@ -0,0 +1,97 @@
|
||||
<script setup>
|
||||
import {Close, Files, House, PieChart, Share, Tools, User} from "@element-plus/icons-vue";
|
||||
import store from "../store/index.js";
|
||||
import {useRouter} from 'vue-router'
|
||||
import everyUser from "@icon-park/vue-next/lib/icons/EveryUser.js";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import axios from "axios";
|
||||
import qs from "qs";
|
||||
let router = useRouter()
|
||||
function logout(){
|
||||
localStorage.clear()
|
||||
delete store.state.config.passcode
|
||||
store.state.websocket.close()
|
||||
store.commit("logout")
|
||||
router.push("login")
|
||||
}
|
||||
function alterPasscode(){
|
||||
ElMessageBox.prompt('请输入新密码:', '修改密码', {
|
||||
confirmButtonText : '确认',
|
||||
cancelButtonText : '取消',
|
||||
inputValue : ''
|
||||
}).then((passcode) => {
|
||||
if(passcode.value.trim() === ""){
|
||||
ElMessage.error("密码不能为空")
|
||||
return
|
||||
}
|
||||
axios.post(store.state.config.host + "alterPasscode?" + qs.stringify(
|
||||
{passcode:passcode.value})).then((res) => {
|
||||
ElMessage.success(res.data.data)
|
||||
store.commit("updatePasscode", passcode.value)
|
||||
router.push("/login")
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-menu :default-active="$router.currentRoute.value.path" router>
|
||||
<el-menu-item index="/index/status/">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<House/>
|
||||
</el-icon>服务器概览
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/index/fileManage/">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<files/>
|
||||
</el-icon>
|
||||
文件管理
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/index/taskManage/">
|
||||
<template #title>
|
||||
<el-icon>
|
||||
<PieChart />
|
||||
</el-icon>
|
||||
任务管理
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/index/siteManage/" v-if="store.state.config.isAdmin">
|
||||
<el-icon>
|
||||
<Tools />
|
||||
</el-icon>
|
||||
服务器管理
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/index/userManage/" v-if="store.state.config.isAdmin">
|
||||
<el-icon>
|
||||
<every-user theme="outline"/>
|
||||
</el-icon>
|
||||
用户管理
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/index/shareManage/">
|
||||
<el-icon>
|
||||
<Share />
|
||||
</el-icon>
|
||||
分享码管理
|
||||
</el-menu-item>
|
||||
<el-menu-item @click="alterPasscode">
|
||||
<el-icon>
|
||||
<User />
|
||||
</el-icon>
|
||||
修改密码
|
||||
</el-menu-item>
|
||||
<el-menu-item @click="logout">
|
||||
<el-icon>
|
||||
<Close />
|
||||
</el-icon>
|
||||
退出登录
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
233
src/views/site-manage/index.vue
Normal file
233
src/views/site-manage/index.vue
Normal file
@ -0,0 +1,233 @@
|
||||
<script setup>
|
||||
|
||||
import {computed, onMounted, ref, watch} from "vue";
|
||||
import store from "../../store/index.js";
|
||||
import {useRouter} from "vue-router";
|
||||
import axios from "axios";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import qs from "qs";
|
||||
import Util from "../../Util/index.js";
|
||||
|
||||
let router = useRouter()
|
||||
let currentSiteId = ref()
|
||||
let hostname = ref('')
|
||||
let domain = ref('')
|
||||
let reverseProxyPrefix = ref('')
|
||||
let size = ref(0)
|
||||
let sizeUnit = ref("GB")
|
||||
let storagePath = ref("")
|
||||
let sites = computed(() => {
|
||||
return store.getters.getSites
|
||||
})
|
||||
let currentSite = computed(() => {
|
||||
let s
|
||||
sites.value.forEach((site) => {
|
||||
if(site.id === currentSiteId.value)
|
||||
s = site
|
||||
})
|
||||
return s
|
||||
})
|
||||
function switchSite(id){
|
||||
currentSiteId.value = id
|
||||
router.replace("/index/siteManage/?id=" + currentSiteId.value)
|
||||
}
|
||||
function submit(){
|
||||
let site = currentSite.value
|
||||
let alterSite = {id:site.id}
|
||||
let adjustSize
|
||||
if(!isNaN(parseInt(size.value)) && size.value !== 0) {
|
||||
adjustSize = Util[sizeUnit.value] * size.value
|
||||
|
||||
if (adjustSize < 0) //减少
|
||||
if (currentSite.availableSpace + adjustSize < 0) {
|
||||
ElMessage.error("减少空间不能小于可分配空间,可以考虑回收用户空间")
|
||||
return
|
||||
}
|
||||
alterSite.availableSpace = adjustSize
|
||||
}
|
||||
|
||||
if(hostname.value.trim() !== '')
|
||||
alterSite.hostname = hostname.value
|
||||
|
||||
if(domain.value.trim() !== '')
|
||||
alterSite.domain = domain.value
|
||||
|
||||
if(reverseProxyPrefix.value.trim() !== '')
|
||||
alterSite.reverseProxyPrefix = reverseProxyPrefix.value
|
||||
|
||||
if(storagePath.value.trim() !== '') {
|
||||
alterSite.storagePath = storagePath.value
|
||||
|
||||
ElMessageBox.confirm("修改服务器存储路径会导致服务器重启,是否继续?", '警告',
|
||||
{ confirmButtonText: "继续",
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
axios.post(store.state.config.host + "manage/site/alter?" + qs.stringify({
|
||||
...alterSite}))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success') {
|
||||
ElMessage.success(res.data.data)
|
||||
store.dispatch('loadSites')
|
||||
hostname.value = ""
|
||||
domain.value = ""
|
||||
reverseProxyPrefix.value = ""
|
||||
size.value = 0
|
||||
}
|
||||
else
|
||||
ElMessage.error(res.data.data)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
axios.post(store.state.config.host + "manage/site/alter?" + qs.stringify({
|
||||
...alterSite}))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success') {
|
||||
ElMessage.success(res.data.data)
|
||||
store.dispatch('loadSites')
|
||||
hostname.value = ""
|
||||
domain.value = ""
|
||||
reverseProxyPrefix.value = ""
|
||||
size.value = 0
|
||||
}
|
||||
else
|
||||
ElMessage.error(res.data.data)
|
||||
})
|
||||
}
|
||||
function unpair(){
|
||||
axios.post(store.state.config.host + "manage/unpair?" + qs.stringify({passcode:store.state.config.passcode,
|
||||
id:currentSiteId.value}))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success') {
|
||||
ElMessage.success(res.data.data)
|
||||
store.dispatch('loadSites')
|
||||
}
|
||||
else
|
||||
ElMessage.error(res.data.data)
|
||||
})
|
||||
}
|
||||
|
||||
watch(router.currentRoute , (value, oldValue)=>{
|
||||
if(value.path !== oldValue.path && value.path.startsWith('/index/siteManage/')) {
|
||||
if (router.currentRoute.value.query.id === undefined) {
|
||||
router.replace(`/index/siteManage/?id=${sites.value[0].id}`)
|
||||
currentSiteId.value = sites.value[0].id
|
||||
} else
|
||||
currentSiteId.value = Number(router.currentRoute.value.query.id)
|
||||
}
|
||||
})
|
||||
onMounted(() => {
|
||||
if(router.currentRoute.value.query.id === undefined) {
|
||||
router.replace(`/index/siteManage/?id=${sites.value[0].id}`)
|
||||
currentSiteId.value = sites.value[0].id
|
||||
}else
|
||||
currentSiteId.value = Number(router.currentRoute.value.query.id)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-menu mode="horizontal" :default-active="currentSiteId">
|
||||
<el-menu-item v-for="site in sites" :index="site.id" @click="switchSite(site.id)" >
|
||||
{{site.hostname}}
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
|
||||
<div v-if="currentSite !== undefined" style="background-color: white; padding-bottom:410px; padding-left: 25px">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
服务器id:{{currentSite.id}}
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
服务器ip:{{currentSite.ip}}
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
域名跟反代前缀,若要去除,直接填"空"
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
当前存储路径:{{currentSite.storagePath}}
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-input v-model="storagePath">
|
||||
<template #prepend>存储路径</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
当前服务器名:{{currentSite.hostname}}
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-input v-model="hostname">
|
||||
<template #prepend>
|
||||
服务器名
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
当前服务器总预分配空间:{{Util.formatSize(currentSite.totalSpace)}} <br>
|
||||
当前服务器可分配空间:{{Util.formatSize(currentSite.availableSpace)}}
|
||||
</el-col>
|
||||
<el-col :span="8" style="line-height: 40px">
|
||||
<el-input v-model="size">
|
||||
<template #prepend>
|
||||
容量调整
|
||||
</template>
|
||||
<template #append>
|
||||
<el-select placeholder="单位" v-model="sizeUnit">
|
||||
<el-option value="KB"/>
|
||||
<el-option value="MB"/>
|
||||
<el-option value="GB"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
当前服务器域名:{{currentSite.domain===undefined?"无":currentSite.domain}}
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-input v-model="domain">
|
||||
<template #prepend>
|
||||
服务器域名
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
当前服务器反向代理前缀:{{currentSite.reverseProxyPrefix===undefined?"无":currentSite.reverseProxyPrefix}}
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-input v-model="reverseProxyPrefix">
|
||||
<template #prepend>
|
||||
反向代理前缀
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-button type="danger" v-if="currentSiteId !== 1" @click="unpair">删除服务器</el-button>
|
||||
<el-button type="primary" @click="submit">提交修改</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
103
src/views/status/index.vue
Normal file
103
src/views/status/index.vue
Normal file
@ -0,0 +1,103 @@
|
||||
<script setup>
|
||||
import SiteStatusCard from '../../components/site-statue-card/index.vue'
|
||||
import {computed, onMounted, ref, watch} from "vue";
|
||||
import store from "../../store/index.js";
|
||||
import {useRouter} from "vue-router";
|
||||
|
||||
let route = useRouter()
|
||||
|
||||
let currentType = ref("all")
|
||||
let statues = ref({})
|
||||
let allSites = computed(() => {
|
||||
return store.state.sites
|
||||
})
|
||||
let onlineSites = computed(() => {
|
||||
return store.getters.getOnlineSites
|
||||
})
|
||||
let offlineSites = computed(() => {
|
||||
return store.getters.getOfflineSites
|
||||
})
|
||||
let currentSites = ref([])
|
||||
let menu = ref()
|
||||
|
||||
function switchType(type){
|
||||
currentType.value = type
|
||||
}
|
||||
function refreshType(){
|
||||
let query = JSON.parse(JSON.stringify(route.currentRoute.value.query))
|
||||
query.type = currentType.value
|
||||
route.push({path: route.currentRoute.value.path, query})
|
||||
switch (currentType.value) {
|
||||
case "all":
|
||||
currentSites.value = allSites.value
|
||||
break
|
||||
case "online":
|
||||
currentSites.value = onlineSites.value
|
||||
break
|
||||
case "offline":
|
||||
currentSites.value = offlineSites.value
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
watch(store.getters.getStatues, () => {
|
||||
statues.value = store.getters.getStatues
|
||||
})
|
||||
watch(currentType, () => {
|
||||
refreshType()
|
||||
})
|
||||
watch(route.currentRoute, () => {
|
||||
if(route.currentRoute.value.path.startsWith("/index/status/")){
|
||||
if(route.currentRoute.value.query.type === undefined) {
|
||||
currentType.value = 'all'
|
||||
|
||||
} else if(route.currentRoute.value.query.type !== 'all' &&
|
||||
route.currentRoute.value.query.type !== 'online' &&
|
||||
route.currentRoute.value.query.type !== 'offline') {
|
||||
currentType.value = 'all'
|
||||
} else
|
||||
currentType.value = route.currentRoute.value.query.type
|
||||
|
||||
refreshType()
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if(route.currentRoute.value.path.startsWith("/index/status/")){
|
||||
if(route.currentRoute.value.query.type === undefined) {
|
||||
currentType.value = 'all'
|
||||
} else if(route.currentRoute.value.query.type !== 'all' &&
|
||||
route.currentRoute.value.query.type !== 'online' &&
|
||||
route.currentRoute.value.query.type !== 'offline') {
|
||||
currentType.value = 'all'
|
||||
} else
|
||||
currentType.value = route.currentRoute.value.query.type
|
||||
refreshType()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-menu mode="horizontal" :default-active="currentType" ref="menu" v-if="store.state.config.isAdmin">
|
||||
<el-menu-item index="all" @click="switchType('all')">全部服务器</el-menu-item>
|
||||
<el-menu-item index="online" @click="switchType('online')">在线服务器</el-menu-item>
|
||||
<el-menu-item index="offline" @click="switchType('offline')">离线服务器</el-menu-item>
|
||||
</el-menu>
|
||||
<el-menu mode="horizontal" default-active="all" router ref="menu" v-else>
|
||||
<el-menu-item index="all">当前服务器</el-menu-item>
|
||||
</el-menu>
|
||||
<el-row>
|
||||
<el-col :span="7" v-for="site in currentSites" :key="site.id">
|
||||
<site-status-card :status="statues[site.id]" :site="site"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
235
src/views/task-manage/index.vue
Normal file
235
src/views/task-manage/index.vue
Normal file
@ -0,0 +1,235 @@
|
||||
<script setup>
|
||||
import {computed, onMounted, ref, watch} from "vue";
|
||||
import store from "../../store/index.js";
|
||||
import Util from "../../Util/index.js";
|
||||
import {useRouter} from "vue-router";
|
||||
import axios from "axios";
|
||||
import qs from "qs";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {InfoFilled, WarningFilled} from "@element-plus/icons-vue";
|
||||
|
||||
let router = useRouter()
|
||||
let currentTasks = ref()
|
||||
let selectedTasks = []
|
||||
let isSelected = ref(false)
|
||||
let currentType = ref('all')
|
||||
let isAdmin = computed(() => {
|
||||
return store.state.config.isAdmin
|
||||
})
|
||||
let allTask = computed(() => {
|
||||
return store.state.tasks
|
||||
})
|
||||
let waitingTask = computed(() => {
|
||||
let tasks = []
|
||||
allTask.value.forEach((task) => {
|
||||
if(task.status === '等待中')
|
||||
tasks.push(task)
|
||||
})
|
||||
return tasks
|
||||
})
|
||||
let proceedingTask = computed(() => {
|
||||
let tasks = []
|
||||
allTask.value.forEach((task) => {
|
||||
if(task.status === '进行中')
|
||||
tasks.push(task)
|
||||
})
|
||||
return tasks
|
||||
})
|
||||
let completeTask = computed(() => {
|
||||
let tasks = []
|
||||
allTask.value.forEach((task) => {
|
||||
if(task.complete)
|
||||
tasks.push(task)
|
||||
})
|
||||
return tasks
|
||||
})
|
||||
let users = computed(() => {
|
||||
return store.state.users
|
||||
})
|
||||
|
||||
function getUsernameById(userid){
|
||||
let username
|
||||
users.value.forEach((user) => {
|
||||
if(user.id === userid)
|
||||
username = user.username
|
||||
})
|
||||
return username
|
||||
}
|
||||
function getSiteBySiteId(id){
|
||||
id = Number(id)
|
||||
let getSite
|
||||
store.getters.getOnlineSites.forEach((site) => {
|
||||
if(site.id === id)
|
||||
getSite = site
|
||||
})
|
||||
return getSite
|
||||
}
|
||||
function handleSelection(tasks){
|
||||
selectedTasks = tasks
|
||||
isSelected.value = tasks.length !== 0;
|
||||
}
|
||||
function cancelTasks() {
|
||||
let taskIds = []
|
||||
selectedTasks.forEach((task) => {
|
||||
taskIds.push(task.taskId)
|
||||
})
|
||||
axios.post(store.state.config.host + "file/cancel", qs.stringify({passcode: store.state.config.passcode, taskIds}, {indices: false}))
|
||||
.then(() => {
|
||||
ElMessage.success("任务移除成功")
|
||||
})
|
||||
store.commit("removeTasks", taskIds)
|
||||
}
|
||||
|
||||
function refreshType(){
|
||||
let query = JSON.parse(JSON.stringify(router.currentRoute.value.query))
|
||||
query.type = currentType.value
|
||||
router.push({path: router.currentRoute.value.path, query})
|
||||
switch (currentType.value) {
|
||||
case "all":
|
||||
currentTasks.value = allTask.value
|
||||
break
|
||||
case "complete":
|
||||
currentTasks.value = completeTask.value
|
||||
break
|
||||
case "proceeding":
|
||||
currentTasks.value = proceedingTask.value
|
||||
break
|
||||
case "waiting":
|
||||
currentTasks.value = waitingTask.value
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function switchType(type){
|
||||
currentType.value = type
|
||||
}
|
||||
|
||||
watch(currentType, () => {
|
||||
refreshType()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if(router.currentRoute.value.path.startsWith("/index/taskManage/")){
|
||||
if(router.currentRoute.value.query.type === undefined) {
|
||||
currentType.value = 'all'
|
||||
} else if(router.currentRoute.value.query.type !== 'all' &&
|
||||
router.currentRoute.value.query.type !== 'online' &&
|
||||
router.currentRoute.value.query.type !== 'offline') {
|
||||
currentType.value = 'all'
|
||||
} else
|
||||
currentType.value = router.currentRoute.value.query.type
|
||||
|
||||
refreshType()
|
||||
}
|
||||
})
|
||||
|
||||
watch(router.currentRoute, () => {
|
||||
if(router.currentRoute.value.path.startsWith('/index/taskManage/')){
|
||||
if(router.currentRoute.value.query.type === undefined) {
|
||||
router.replace('/index/taskManage/?type=all')
|
||||
currentType.value = 'all'
|
||||
} else if(router.currentRoute.value.query.type !== 'all' &&
|
||||
router.currentRoute.value.query.type !== 'complete' &&
|
||||
router.currentRoute.value.query.type !== 'proceeding' &&
|
||||
router.currentRoute.value.query.type !== 'waiting') {
|
||||
router.replace("/index/taskManage/?type=all")
|
||||
currentType.value = 'all'
|
||||
} else
|
||||
currentType.value = router.currentRoute.value.query.type
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<el-menu mode="horizontal" :default-active="currentType">
|
||||
<el-menu-item index="all" @click="switchType('all')">全部</el-menu-item>
|
||||
<el-menu-item index="complete" @click="switchType('complete')">已完成/已停止</el-menu-item>
|
||||
<el-menu-item index="proceeding" @click="switchType('proceeding')">进行中</el-menu-item>
|
||||
<el-menu-item index="waiting" @click="switchType('waiting')">等待中</el-menu-item>
|
||||
</el-menu>
|
||||
<el-button-group>
|
||||
<el-button @click="cancelTasks" :disabled="!isSelected">删除/取消</el-button>
|
||||
</el-button-group>
|
||||
<el-table :data="currentTasks" empty-text="当前分类没有任务" :onSelectionChange="handleSelection" row-key="taskId">
|
||||
<el-table-column type="selection" reserve-selection />
|
||||
<el-table-column label="任务id" width="75px">
|
||||
<template #default="scope">
|
||||
{{scope.row.taskId}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件名">
|
||||
<template #default="scope">
|
||||
{{scope.row.filename}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="用户" v-if="isAdmin">
|
||||
<template #default="scope">
|
||||
{{getUsernameById(scope.row.userid)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="任务类型" width="100px">
|
||||
<template #default="scope">
|
||||
{{scope.row.type}}
|
||||
<el-popover width="450px" v-if="(isAdmin || scope.row.type !== '导入')">
|
||||
<span v-if="scope.row.type === '打包'">
|
||||
服务器:{{getSiteBySiteId(scope.row.siteId).hostname}}<br>
|
||||
源文件:{{scope.row.paths}}<br>
|
||||
目标文件:{{scope.row.targetPath}}
|
||||
</span>
|
||||
<span v-if="scope.row.type === '转移'">
|
||||
源服务器:{{getSiteBySiteId(scope.row.sender).hostname}}<br>
|
||||
源路径:{{scope.row.sourcePath}}<br>
|
||||
目标服务器:{{getSiteBySiteId(scope.row.receiver).hostname}}<br>
|
||||
目标路径:{{scope.row.targetPath}}
|
||||
</span>
|
||||
<span v-if="scope.row.type === '解包'">
|
||||
服务器:{{getSiteBySiteId(scope.row.siteId).hostname}}<br>
|
||||
源文件:{{scope.row.sourcePath + scope.row.filename}}<br>
|
||||
目标路径:{{scope.row.targetPath}}
|
||||
</span>
|
||||
<template #reference>
|
||||
<el-icon>
|
||||
<InfoFilled />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="任务进度">
|
||||
<template #default="scope">
|
||||
<el-progress :percentage="scope.row.percentage"></el-progress>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="任务状态">
|
||||
<template #default="scope">
|
||||
{{scope.row.status}}
|
||||
<el-popover trigger="hover" v-if="scope.row.status === '任务失败'">
|
||||
{{scope.row.cause}}
|
||||
<template #reference>
|
||||
<el-icon>
|
||||
<WarningFilled />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="任务速度">
|
||||
<template #default="scope">
|
||||
{{Util.formatSize(scope.row.speed)+"/S"}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="任务大小">
|
||||
<template #default="scope">
|
||||
{{Util.formatSize(scope.row.total)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
445
src/views/user-manage/index.vue
Normal file
445
src/views/user-manage/index.vue
Normal file
@ -0,0 +1,445 @@
|
||||
<script setup>
|
||||
import {computed, ref, watch, onMounted} from "vue"
|
||||
import store from "../../store/index.js";
|
||||
import Util from "../../Util/index.js";
|
||||
import {router} from "../../router/index.js";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import axios from "axios";
|
||||
import qs from "qs";
|
||||
|
||||
let currentUserId = ref()
|
||||
let currentUser = computed(() => {
|
||||
let u
|
||||
users.value.forEach((user) => {
|
||||
if(user.id === currentUserId.value)
|
||||
u = user
|
||||
})
|
||||
return u
|
||||
})
|
||||
|
||||
let sites = computed(() => {
|
||||
let sites = []
|
||||
if(!isNewUser.value)
|
||||
sites.push({id: 0, hostname: '不修改服务器', availableSpace: 0})
|
||||
else
|
||||
siteId.value = 1
|
||||
sites.push(...store.state.sites)
|
||||
return sites
|
||||
})
|
||||
let users = computed(() => {
|
||||
let us = []
|
||||
store.state.users.forEach((user) => {
|
||||
if(user.id !== 1)
|
||||
us.push(user)
|
||||
})
|
||||
return us
|
||||
})
|
||||
let isNewUser = ref(false)
|
||||
let username = ref('')
|
||||
let passcode = ref('')
|
||||
let totalSpace = ref(0)
|
||||
let spaceUnit = ref("GB")
|
||||
let storagePath = ref('')
|
||||
let siteId = ref(0)
|
||||
|
||||
function setToDefault(){
|
||||
username.value = ""
|
||||
passcode.value = ""
|
||||
totalSpace.value = 0
|
||||
storagePath.value = ""
|
||||
siteId.value = 0
|
||||
}
|
||||
function getSite(siteId){
|
||||
let site
|
||||
sites.value.forEach((s) => {
|
||||
if(s.id === siteId)
|
||||
site = s
|
||||
})
|
||||
return site
|
||||
}
|
||||
|
||||
function switchUser(userid){
|
||||
isNewUser.value = userid === -1;
|
||||
currentUserId.value = userid
|
||||
setToDefault()
|
||||
}
|
||||
function getSiteName(siteId){
|
||||
let name
|
||||
sites.value.forEach((site) => {
|
||||
if(site.id === siteId)
|
||||
name = site.hostname
|
||||
})
|
||||
return name
|
||||
}
|
||||
|
||||
function submit(){
|
||||
if(isNewUser.value)
|
||||
newUser()
|
||||
else
|
||||
alterUser()
|
||||
}
|
||||
|
||||
function newUser(){
|
||||
let newUser = {}
|
||||
let size
|
||||
|
||||
if(username.value.trim() !== ''){
|
||||
let error = ''
|
||||
users.value.forEach((u) => {
|
||||
if(u.username === username.value)
|
||||
error = '不允许重复用户名'
|
||||
})
|
||||
if(error !== '') {
|
||||
ElMessage.error(error)
|
||||
return
|
||||
}
|
||||
newUser.username = username.value
|
||||
} else {
|
||||
ElMessage.error("请输入用户名")
|
||||
return
|
||||
}
|
||||
|
||||
if(passcode.value.trim() !== '')
|
||||
newUser.passcode = passcode.value
|
||||
else {
|
||||
ElMessage.error("请输入密码")
|
||||
return
|
||||
}
|
||||
|
||||
if(isNaN(parseInt(totalSpace.value))){ //输入的不是数字 两种情况:1.字符串 2.空白
|
||||
ElMessage.error("容量大小输入错误,请输入数字")
|
||||
return
|
||||
} else {
|
||||
size = totalSpace.value * Util[spaceUnit.value]
|
||||
|
||||
if(size <= 0){
|
||||
ElMessage.error('请输入正确容量')
|
||||
return
|
||||
} else { //新建用户 不能超过服务器当前可用空间
|
||||
let site = getSite(siteId.value)
|
||||
if(size > site.availableSpace){
|
||||
ElMessage.error('容量调整错误,不能超过服务器可分配空间,请先释放其他用户空间或增大服务器可分配看见后再尝试')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newUser.availableSpace = size
|
||||
newUser.totalSpace = size
|
||||
}
|
||||
|
||||
if(storagePath.value.trim() !== '') {
|
||||
if(!(storagePath.value.endsWith("/") || storagePath.value.endsWith("\\")))
|
||||
storagePath.value += '/'
|
||||
|
||||
if(storagePath.value.startsWith("/") || storagePath.value.startsWith("\\"))
|
||||
storagePath.value = storagePath.value.substring(0)
|
||||
|
||||
newUser.storagePath = storagePath.value
|
||||
} else {
|
||||
ElMessage.error("请输入存储路径")
|
||||
return
|
||||
}
|
||||
|
||||
if(siteId.value !== 0){
|
||||
let site = getSite(siteId.value)
|
||||
if(site.availableSpace < newUser.totalSpace){
|
||||
ElMessage.error("分配服务器可分配空间不足,请重新输入容量")
|
||||
return
|
||||
}
|
||||
|
||||
newUser.siteId = siteId.value
|
||||
} else {
|
||||
ElMessage.error("请绑定服务器")
|
||||
return
|
||||
}
|
||||
|
||||
axios.post(store.state.config.host + "manage/user/create?" + qs.stringify({...newUser}))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success'){
|
||||
ElMessage.success(res.data.data)
|
||||
setToDefault()
|
||||
store.dispatch("loadUsers")
|
||||
store.dispatch("loadSites")
|
||||
} else {
|
||||
ElMessage.error(res.data.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function alterUser(){
|
||||
let alterUser = {id:currentUserId.value}
|
||||
let adjustSize
|
||||
|
||||
if(username.value.trim() !== ''){
|
||||
let error = ''
|
||||
users.value.forEach((u) => {
|
||||
if(u.id !== currentUserId.value && u.username === username.value)
|
||||
error = '重命名失败,不允许重复用户名'
|
||||
})
|
||||
if(error !== '') {
|
||||
ElMessage.error(error)
|
||||
return
|
||||
}
|
||||
alterUser.username = username.value
|
||||
}
|
||||
|
||||
if(passcode.value.trim() !== '')
|
||||
alterUser.passcode = passcode.value
|
||||
|
||||
if(isNaN(parseInt(totalSpace.value))){ //输入的不是数字 两种情况:1.字符串 2.空白
|
||||
if(totalSpace.value.trim() !== ''){
|
||||
ElMessage.error("容量大小输入错误,请输入数字")
|
||||
return
|
||||
}
|
||||
} else if(totalSpace.value !== 0) {
|
||||
adjustSize = totalSpace.value * Util[spaceUnit.value]
|
||||
|
||||
if(adjustSize < 0){ //减少 不能小于当前可用空间
|
||||
if(adjustSize + currentUser.value.availableSpace < 0) {
|
||||
ElMessage.error('容量调整错误,释放空间不能小于用户可用空间,请先删除用户空间并同步后再尝试')
|
||||
return
|
||||
}
|
||||
} else { //增加 不能超过服务器当前可用空间
|
||||
let site = getSite(currentUser.value.siteId)
|
||||
if(adjustSize > site.availableSpace){
|
||||
ElMessage.error('容量调整错误,增加空间不能超过服务器可分配空间,请先释放其他用户空间或增大服务器可分配看见后再尝试')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
alterUser.totalSpace = adjustSize
|
||||
}
|
||||
|
||||
if(storagePath.value.trim() !== '') {
|
||||
if ('totalSpace' in alterUser) {
|
||||
ElMessage.error("不允许同时调整容量以及存储路径,请重新输入")
|
||||
return
|
||||
}
|
||||
|
||||
if(!(storagePath.value.endsWith("/") || storagePath.value.endsWith("\\")))
|
||||
storagePath.value += '/'
|
||||
|
||||
alterUser.storagePath = storagePath.value
|
||||
}
|
||||
|
||||
if(siteId.value !== 0){
|
||||
if('totalSpace' in alterUser){
|
||||
ElMessage.error("不允许同时调整容量以及绑定服务器,请重新输入")
|
||||
return
|
||||
}
|
||||
if('storagePath' in alterUser){
|
||||
ElMessage.error("不允许同时调整存储路径以及绑定服务器,请重新输入")
|
||||
return
|
||||
}
|
||||
|
||||
ElMessageBox.confirm('请注意,用户换绑服务器会导致原文件直接删除,请确保用户的文件已备份或转移到目标服务器了!',
|
||||
'警告',
|
||||
{
|
||||
confirmButtonText: "确认",
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(() => {
|
||||
alterUser.siteId = siteId.value
|
||||
axios.post(store.state.config.host + "manage/user/alter?" + qs.stringify(alterUser))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success') {
|
||||
ElMessage.success(res.data.data)
|
||||
setToDefault()
|
||||
store.dispatch('loadUsers')
|
||||
store.dispatch('loadSites')
|
||||
} else
|
||||
ElMessage.error(res.data.data)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if(Object.keys(alterUser).length === 1)
|
||||
return
|
||||
|
||||
axios.post(store.state.config.host + "manage/user/alter?" + qs.stringify(alterUser))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success') {
|
||||
ElMessage.success(res.data.data)
|
||||
setToDefault()
|
||||
store.dispatch('loadUsers')
|
||||
store.dispatch('loadSites')
|
||||
} else
|
||||
ElMessage.error(res.data.data)
|
||||
})
|
||||
}
|
||||
|
||||
function removeUser(){
|
||||
ElMessageBox.confirm("删除用户会直接删除用户的存储文件夹,请确保用户数据已备份,是否继续?", '警告',
|
||||
{
|
||||
confirmButtonText: "删除",
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
axios.post(store.state.config.host + "manage/user/remove?" + qs.stringify({userid:currentUserId.value}))
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success'){
|
||||
ElMessage.success("用户删除成功")
|
||||
store.dispatch("loadSites")
|
||||
store.dispatch("loadUsers")
|
||||
switchUser(users.value[0].id)
|
||||
} else {
|
||||
ElMessage.error(res.data.data)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function verifySpace(){
|
||||
axios.post(store.state.config.host + "manage/user/verifySpace?userid=" + currentUserId.value)
|
||||
.then((res) => {
|
||||
if(res.data.result === 'success') {
|
||||
ElMessage.success(res.data.data)
|
||||
store.dispatch('loadUsers')
|
||||
}
|
||||
else
|
||||
ElMessage.error(res.data.data)
|
||||
})
|
||||
}
|
||||
|
||||
function siteLabel(site){
|
||||
if(site.id === 0)
|
||||
return '不修改服务器'
|
||||
else if(site.id === currentUser.value.siteId)
|
||||
return '当前绑定服务器:' + site.id + ' ' + site.hostname + ' 剩余空间:' + Util.formatSize(site.availableSpace)
|
||||
else
|
||||
return site.id + ' ' + site.hostname + ' 剩余空间:' + Util.formatSize(site.availableSpace)
|
||||
}
|
||||
|
||||
watch(router.currentRoute, (now, old) => {
|
||||
if(now.path !== old.path && now.path.startsWith("/index/userManage/")){
|
||||
if(router.currentRoute.value.query.id === undefined){
|
||||
router.replace(`/index/userManage/?id=${users.value[0].id}`)
|
||||
currentUserId.value = Number(users.value[0].id)
|
||||
|
||||
} else
|
||||
currentUserId.value = Number(users.value[0].id)
|
||||
|
||||
if(users.value[0].id === -1)
|
||||
isNewUser.value = true
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if(router.currentRoute.value.query.id === undefined){
|
||||
router.replace(`/index/userManage/?id=${users.value[0].id}`)
|
||||
currentUserId.value = Number(users.value[0].id)
|
||||
} else
|
||||
currentUserId.value = Number(router.currentRoute.value.query.id)
|
||||
|
||||
if(currentUserId.value === -1)
|
||||
isNewUser.value = true
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-menu mode="horizontal" :default-active="currentUserId">
|
||||
<el-menu-item v-for="user in users" :index="user.id" @click="switchUser(user.id)">
|
||||
{{user.username}}
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
|
||||
<div v-if="currentUser !== undefined" style="background-color: white; padding-bottom:400px; padding-left: 25px;">
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<span v-if="!isNewUser">
|
||||
用户名:{{currentUser.username}}
|
||||
</span>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-input v-model="username">
|
||||
<template #prepend>
|
||||
{{isNewUser?'':'新'}}用户名
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<span v-if="!isNewUser">
|
||||
密码:{{currentUser.passcode}}
|
||||
</span>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-input v-model="passcode">
|
||||
<template #prepend>
|
||||
{{isNewUser?'':'新'}}密码
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<span v-if="!isNewUser">
|
||||
当前可用空间:{{Util.formatSize(currentUser.availableSpace)}}<br>
|
||||
总分配空间:{{Util.formatSize(currentUser.totalSpace)}}
|
||||
</span>
|
||||
</el-col>
|
||||
<el-col :span="8" style="line-height: 37px">
|
||||
<el-input v-model="totalSpace">
|
||||
<template #prepend>
|
||||
{{(isNewUser?'分配':'调整') + '空间'}}
|
||||
</template>
|
||||
<template #append>
|
||||
<el-select v-model="spaceUnit" style="width: 100px">
|
||||
<el-option value="KB"/>
|
||||
<el-option value="MB"/>
|
||||
<el-option value="GB"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<span v-if="!isNewUser">
|
||||
存储路径:{{currentUser.storagePath}}
|
||||
</span>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-input v-model="storagePath">
|
||||
<template #prepend>
|
||||
{{isNewUser?'':'新'}}存储路径
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
<span v-if="!isNewUser">
|
||||
绑定服务器:{{getSiteName(currentUser.siteId)}}
|
||||
</span>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-select v-model="siteId" style="width: 425px">
|
||||
<template #prefix>
|
||||
{{isNewUser?'绑定服务器':''}}
|
||||
</template>
|
||||
<el-option v-for="site in sites" :label="siteLabel(site)" :value="site.id" :disabled="site.id === currentUser.siteId || site.availableSpace === 0"/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="8">
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-button @click="submit" type="primary">提交{{isNewUser?'新建':'修改'}}</el-button>
|
||||
<el-button @click="removeUser" v-show="!isNewUser" type="danger">删除用户</el-button>
|
||||
<el-button @click="verifySpace" v-show="!isNewUser">校准用户可用空间</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
35
vite.config.js
Normal file
35
vite.config.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import IconsResolver from 'unplugin-icons/resolver'
|
||||
import Icons from 'unplugin-icons/vite'
|
||||
import {ElementPlusResolver} from "unplugin-vue-components/resolvers";
|
||||
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
|
||||
plugins: [vue(),
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [ElementPlusResolver(),IconsResolver({
|
||||
prefix: 'i'
|
||||
})],
|
||||
}),Icons({
|
||||
compiler:"vue3",
|
||||
autoInstall: true
|
||||
})],
|
||||
server:{
|
||||
proxy: {
|
||||
"/api":{
|
||||
target: "https://sns.lionwebsite.xyz/",
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api/, "")
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
Loading…
Reference in New Issue
Block a user