项目完成
This commit is contained in:
commit
50fc0045e7
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
21
README.md
Normal file
21
README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# 多模态文件管理系统前端工程 filemanage-frontend
|
||||
|
||||
## 初始化项目
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
## 自定义项目配置
|
||||
1.修改main.js下的后端api请求路径前缀BASE_URL
|
||||
|
||||
2.修改vue.config.js下的前端工程运行端口port
|
||||
|
||||
## 开发环境下启动
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
## 打包工程到生产环境部署
|
||||
```
|
||||
npm run build
|
||||
```
|
5
babel.config.js
Normal file
5
babel.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
19
jsconfig.json
Normal file
19
jsconfig.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
20320
package-lock.json
generated
Normal file
20320
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
package.json
Normal file
47
package.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "filemanage-frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"axios": "^1.6.7",
|
||||
"core-js": "^3.8.3",
|
||||
"element-plus": "^2.6.1",
|
||||
"vue": "^3.2.13",
|
||||
"vue-router": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.16",
|
||||
"@babel/eslint-parser": "^7.12.16",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/vue3-essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "@babel/eslint-parser"
|
||||
},
|
||||
"rules": {}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead",
|
||||
"not ie 11"
|
||||
]
|
||||
}
|
BIN
public/background.png
Normal file
BIN
public/background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 464 KiB |
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
18
public/index.html
Normal file
18
public/index.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<!-- <title><%= htmlWebpackPlugin.options.title %></title>-->
|
||||
<title>多模态文件管理系统</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
23
src/App.vue
Normal file
23
src/App.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'App'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 样式可以根据需要修改 */
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-align: center;
|
||||
color: #2c3e50;
|
||||
}
|
||||
</style>
|
BIN
src/assets/logo.png
Normal file
BIN
src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
307
src/components/FilePage.vue
Normal file
307
src/components/FilePage.vue
Normal file
@ -0,0 +1,307 @@
|
||||
<template>
|
||||
<!-- 文件上传表单 -->
|
||||
<el-upload
|
||||
class="upload-demo"
|
||||
:action="uploadUrl"
|
||||
:with-credentials="true"
|
||||
:on-success="handleUploadSuccess"
|
||||
:before-upload="beforeUpload"
|
||||
drag
|
||||
>
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
<template v-slot:tip>
|
||||
<div class="el-upload__tip">或将文件拖到此处上传</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
|
||||
<el-divider></el-divider>
|
||||
|
||||
<el-row>
|
||||
<el-col :span="18">
|
||||
<el-form ref="searchForm" :model="searchForm" >
|
||||
<el-row>
|
||||
<el-input v-model="searchForm.name" placeholder="请输入要查询的文件名" style="width: 240px; padding-right: 10px"></el-input>
|
||||
<el-button type="primary" @click="getFiles" :icon="Search"></el-button>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<!-- 批量操作按钮 -->
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="handleBatchDownload">批量下载</el-button>
|
||||
<el-button type="danger" @click="handleBatchDelete">批量删除</el-button>
|
||||
</el-button-group>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 图片预览模态框 -->
|
||||
<el-dialog :title="previewImage.name" v-model="previewVisible" width="50%" @close="handleCloseDialog" center>
|
||||
<img :src="previewImage.image" style="width: 100%" alt="预览图片" />
|
||||
</el-dialog>
|
||||
|
||||
<!-- 文件列表 -->
|
||||
<el-table
|
||||
:data="files"
|
||||
border
|
||||
:v-loading="loading"
|
||||
:default-sort="{ prop: 'name', order: 'ascending' }"
|
||||
@selection-change="handleSelectionChange"
|
||||
:show-overflow-tooltip=true
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="序号" type="index" />
|
||||
<el-table-column label="文件名" prop="name" sortable>
|
||||
<template v-slot="{ row }">
|
||||
<span v-if="row.type === 0" @click="getPreviewImage(row)" style="color: blue">{{ row.name }}</span>
|
||||
<span v-else>{{ row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件大小" prop="size" sortable>
|
||||
<template v-slot="{ row }">
|
||||
{{getFileSize(row.size)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" label="文件类型" sortable>
|
||||
<template v-slot="{ row }">
|
||||
{{ getFileType(row.type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最后更新时间" prop="updateTime" sortable></el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template v-slot="{ row }">
|
||||
<el-button type="primary" size="small" @click="downloadFile(row)">下载</el-button>
|
||||
<el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[5, 10, 20, 50]"
|
||||
:total="total"
|
||||
:background="true"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange">
|
||||
</el-pagination>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {BASE_URL} from "@/main";
|
||||
import axios from "axios";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import { Search } from "@element-plus/icons-vue"
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
Search() {
|
||||
return Search
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
|
||||
selectedRows: [], // 存储选中的行数据
|
||||
searchForm: {name: ''},
|
||||
files: [], // 所有文件数据
|
||||
loading: false, // 加载状态
|
||||
|
||||
previewVisible: false,
|
||||
previewImage: {
|
||||
name: '',
|
||||
image: ''
|
||||
},
|
||||
|
||||
uploadUrl: BASE_URL+'/file/upload',
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCloseDialog() {
|
||||
this.previewVisible =false;
|
||||
this.previewImage = {
|
||||
name: '',
|
||||
image: ''
|
||||
};
|
||||
},
|
||||
|
||||
//图片预览
|
||||
getPreviewImage(file) {
|
||||
axios.get(BASE_URL + '/file/preview/' + file.id, {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.code === 1) {
|
||||
this.previewVisible = true;
|
||||
this.previewImage.name = file.name;
|
||||
this.previewImage.image = response.data.data;
|
||||
} else {
|
||||
ElMessage.error('预览失败,'+response.data.msg);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('请求失败,'+error);
|
||||
});
|
||||
},
|
||||
|
||||
// 多选框选中状态变化时
|
||||
handleSelectionChange(selection) {
|
||||
this.selectedRows = selection; // 更新选中的行数据
|
||||
},
|
||||
|
||||
handleBatchDownload() {
|
||||
this.selectedRows.forEach(item => {
|
||||
this.downloadFile(item);
|
||||
});
|
||||
},
|
||||
handleBatchDelete() {
|
||||
let ids = [];
|
||||
this.selectedRows.forEach(item => {
|
||||
ids.push(item.id);
|
||||
});
|
||||
axios.put(BASE_URL+'/file'+'/1', ids, {withCredentials: true})
|
||||
.then(response => {
|
||||
if(response.data.code === 1){
|
||||
ElMessage.success(response.data.data);
|
||||
} else {
|
||||
ElMessage.error('文件批量删除失败,'+response.data.msg);
|
||||
}
|
||||
this.getFiles();
|
||||
})
|
||||
.catch(error => {
|
||||
//console.log(error);
|
||||
ElMessage.error('请求失败:'+error);
|
||||
});
|
||||
},
|
||||
|
||||
handleUploadSuccess() {
|
||||
this.getFiles();
|
||||
},
|
||||
beforeUpload() {
|
||||
// 在这里可以添加一些文件上传前的逻辑,比如文件大小限制等
|
||||
return true; // 返回 true 表示允许上传
|
||||
},
|
||||
|
||||
// 处理每页显示条数变化
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size;
|
||||
this.currentPage = 1; // 重置当前页码为1
|
||||
this.getFiles();
|
||||
},
|
||||
// 处理当前页码变化
|
||||
handleCurrentChange(page) {
|
||||
this.currentPage = page;
|
||||
this.getFiles();
|
||||
},
|
||||
handleDelete(file) {
|
||||
ElMessageBox.confirm(
|
||||
'确认要删除该文件吗(移入回收站)?',
|
||||
'Warning',
|
||||
{
|
||||
confirmButtonText: '是',
|
||||
cancelButtonText: '否',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.logicDeleteFile(file);
|
||||
})
|
||||
},
|
||||
|
||||
// 获取文件列表数据
|
||||
getFiles() {
|
||||
this.loading = true; // 显示加载状态
|
||||
let url = BASE_URL + '/file/page/'+this.currentPage+'/'+this.pageSize+'/0';
|
||||
axios.get(url, {params:{name: this.searchForm.name}, withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.code === 1) {
|
||||
// this.files = response.data.data; // 设置文件列表数据
|
||||
this.files = response.data.data.records; // 设置文件列表数据
|
||||
this.total = response.data.data.total;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
//console.error('文件列表获取失败', error);
|
||||
ElMessage.error('文件列表获取失败,'+error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false; // 隐藏加载状态
|
||||
});
|
||||
},
|
||||
getFileSize(size) {
|
||||
if(size/1024 < 1.0)
|
||||
return size.toFixed(1)+'B';
|
||||
if(size/(1024*1024) < 1.0)
|
||||
return (size/(1024)).toFixed(1)+'KB';
|
||||
if(size/(1024*1024*1024) < 1.0)
|
||||
return (size/(1024*1024)).toFixed(1)+'MB';
|
||||
return (size/(1024*1024*1024)).toFixed(1)+'GB';
|
||||
},
|
||||
getFileType(type) {
|
||||
// 根据文件类型代码返回对应的文字描述
|
||||
switch (type) {
|
||||
case 0:
|
||||
return '图片';
|
||||
case 1:
|
||||
return '视频';
|
||||
case 2:
|
||||
return '音频';
|
||||
case 3:
|
||||
return '文档';
|
||||
default:
|
||||
return '其他';
|
||||
}
|
||||
},
|
||||
downloadFile(file) {
|
||||
// 下载文件
|
||||
axios.get(BASE_URL+'/file/download/'+file.id, {
|
||||
responseType: 'blob',
|
||||
withCredentials: true
|
||||
}).then(response => {
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.setAttribute('download', file.name);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
// 下载完成后销毁 URL 对象
|
||||
window.URL.revokeObjectURL(url);
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
if(error.response.status === 404){
|
||||
ElMessage.error('文件'+file.name+'下载失败,文件不存在或被删除');
|
||||
this.getFiles();
|
||||
return;
|
||||
}
|
||||
ElMessage.error("请求失败:"+error);
|
||||
});
|
||||
},
|
||||
logicDeleteFile(file) {
|
||||
// 删除文件的操作
|
||||
//console.log('删除文件:', file);
|
||||
axios.put(BASE_URL+'/file/'+file.id+'/1', {}, {withCredentials: true})
|
||||
.then(response => {
|
||||
if(response.data.code === 1){
|
||||
ElMessage.success(response.data.data);
|
||||
} else {
|
||||
ElMessage.error('文件'+file.name+'删除失败,'+response.data.msg);
|
||||
}
|
||||
this.getFiles();
|
||||
})
|
||||
.catch(error => {
|
||||
//console.log(error);
|
||||
ElMessage.error('请求失败:'+error);
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getFiles();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
126
src/components/HomePage.vue
Normal file
126
src/components/HomePage.vue
Normal file
@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<div class="home-container">
|
||||
<!-- 左上角个人信息简要和退出按钮 -->
|
||||
<div class="user-info">
|
||||
<span style="padding-right: 5px">{{ currentUser.username }}</span>
|
||||
<el-button @click="logout" plain>退出</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 左侧菜单 -->
|
||||
<el-aside width="200px" style="margin-top: 50px;">
|
||||
<el-menu :default-active="currentMenu" class="el-menu-vertical-demo" @select="handleMenuSelect">
|
||||
<el-menu-item index="myFiles">我的文件</el-menu-item>
|
||||
<el-menu-item index="profile">个人中心</el-menu-item>
|
||||
<el-menu-item index="recycleBin">回收站</el-menu-item>
|
||||
<el-menu-item index="management" v-if="currentUser.isAdmin === 1">管理</el-menu-item>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<el-main>
|
||||
<!-- 根据菜单切换展示不同内容 -->
|
||||
<div v-if="currentMenu === 'myFiles'">
|
||||
<FilePage/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentMenu === 'profile'">
|
||||
<ProfilePage :currentUser="currentUser" @update:currentUser="updateUsername"/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentMenu === 'recycleBin'">
|
||||
<!-- 回收站内容 -->
|
||||
<RecycleBinPage/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="currentMenu === 'management'">
|
||||
<ManagementPage/>
|
||||
</div>
|
||||
</el-main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import {BASE_URL} from "@/main";
|
||||
import {ElMessage} from "element-plus";
|
||||
import ProfilePage from '@/components/ProfilePage.vue'
|
||||
import ManagementPage from '@/components/ManagementPage.vue'
|
||||
import FilePage from '@/components/FilePage.vue'
|
||||
import RecycleBinPage from '@/components/RecycleBinPage.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ProfilePage,
|
||||
ManagementPage,
|
||||
FilePage,
|
||||
RecycleBinPage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentUser: {
|
||||
email: '',
|
||||
username: '',
|
||||
isAdmin: 0,
|
||||
storageUsed: 0,
|
||||
storageTotal: 0
|
||||
}, // 从服务端获取的用户信息
|
||||
currentMenu: 'myFiles', // 当前选中的菜单
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleMenuSelect(index) {
|
||||
this.currentMenu = index;
|
||||
},
|
||||
|
||||
getUser() {
|
||||
axios.get(BASE_URL+'/user', {withCredentials: true})
|
||||
.then(response => {
|
||||
if(response.data.code === 1) {
|
||||
this.currentUser = response.data.data;
|
||||
} else
|
||||
ElMessage.error('用户信息获取失败,'+response.data.msg);
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('请求失败,'+error);
|
||||
});
|
||||
},
|
||||
logout() {
|
||||
// 实现登出逻辑,例如清除本地存储的用户信息等
|
||||
axios.post(BASE_URL+'/user/logout', {}, {withCredentials: true})
|
||||
.then(response => {
|
||||
if(response.data.code === 1)
|
||||
this.$router.push({name: 'Login'})
|
||||
else ElMessage.error('退出失败,'+response.data.msg)
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('请求失败:'+error);
|
||||
});
|
||||
},
|
||||
|
||||
//接受子组件传递的数据更新
|
||||
updateUsername(newUsername){
|
||||
this.currentUser.username = newUsername;
|
||||
},
|
||||
updateCloudStorageConfig(newConfig){
|
||||
this.cloudStorageConfig = newConfig;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getUser();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
</style>
|
148
src/components/LoginPage.vue
Normal file
148
src/components/LoginPage.vue
Normal file
@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="background"></div>
|
||||
<div class="content">
|
||||
<h1 class="welcome-title">欢迎使用文件管理系统</h1>
|
||||
<el-form :model="loginForm" :rules="rules">
|
||||
<el-form-item label="邮箱/用户名" prop="email">
|
||||
<el-input v-model="loginForm.email" placeholder="请输入邮箱/用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input type="password" v-model="loginForm.password" placeholder="请输入密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="验证码" prop="captcha">
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<el-input v-model="loginForm.captcha" placeholder="请输入验证码"></el-input>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<img :src="captchaImageUrl" @click="refreshCaptcha" alt="验证码" style="cursor: pointer; width: 130px; height: 48px;">
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" class="login-button" @click="login">登录</el-button>
|
||||
<el-button @click="goToRegister" plain>注册</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ElForm, ElFormItem, ElInput, ElButton, ElRow, ElCol, ElMessage } from 'element-plus';
|
||||
import axios from "axios";
|
||||
import { BASE_URL } from "@/main";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElButton,
|
||||
ElRow,
|
||||
ElCol
|
||||
},
|
||||
mounted() {
|
||||
this.refreshCaptcha();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loginForm: {
|
||||
email: '',
|
||||
password: '',
|
||||
captcha: '',
|
||||
verKey: ''
|
||||
},
|
||||
rules: {
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' }
|
||||
],
|
||||
captcha: [
|
||||
{ required: true, message: '请输入验证码', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
captchaImageUrl: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
login() {
|
||||
if(!this.loginForm.email || !this.loginForm.password || !this.loginForm.captcha){
|
||||
ElMessage.error('请填写必填项');
|
||||
return;
|
||||
}
|
||||
//console.log('登录', this.loginForm);
|
||||
axios.post(BASE_URL + '/user/login', this.loginForm, {withCredentials:true})
|
||||
.then(response => {
|
||||
// 请求成功
|
||||
if (response.data.code === 1) {
|
||||
//console.log('登录成功', response.data);
|
||||
ElMessage.success(response.data.data);
|
||||
this.$router.push({name:'Home'});
|
||||
} else if (response.data.code === 0) {
|
||||
//console.log('登录失败', response.data);
|
||||
ElMessage.error('登录失败,' + response.data.msg)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// 请求失败
|
||||
ElMessage.error('请求失败:'+ error);
|
||||
// 根据错误信息进行处理,比如提示用户或者重试等
|
||||
});
|
||||
},
|
||||
refreshCaptcha() {
|
||||
// 发送请求获取新的图片验证码
|
||||
axios.get(BASE_URL + '/user/captcha')
|
||||
.then(response => {
|
||||
let data = JSON.parse(response.data.data);
|
||||
this.loginForm.verKey = data.key;
|
||||
this.captchaImageUrl = data.image;
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('验证码获取异常:'+error);
|
||||
});
|
||||
//console.log('刷新验证码');
|
||||
},
|
||||
goToRegister() {
|
||||
//console.log('跳转到注册页面');
|
||||
this.$router.push({name:'Registry'});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('/public/background.png');
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 400px;
|
||||
padding: 20px;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
304
src/components/ManagementPage.vue
Normal file
304
src/components/ManagementPage.vue
Normal file
@ -0,0 +1,304 @@
|
||||
<template>
|
||||
<h3 style="text-align: left">新增存储策略配置</h3>
|
||||
<div style="text-align: left">
|
||||
<el-radio-group v-model="storageType" @change="handleChange">
|
||||
<!-- <el-radio :value="1">服务端本地存储</el-radio>-->
|
||||
<el-radio :value="2">云存储(腾讯云)</el-radio>
|
||||
</el-radio-group>
|
||||
<!-- <el-button type="primary" @click="handleSave">新增</el-button>-->
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
:data="configs"
|
||||
border
|
||||
:v-loading="loading"
|
||||
:show-overflow-tooltip=true
|
||||
>
|
||||
<el-table-column label="序号" type="index" />
|
||||
<el-table-column label="存储策略名" prop="name" sortable width="120"></el-table-column>
|
||||
<el-table-column label="策略类型" prop="type" sortable width="120">
|
||||
<template v-slot="{ row }">
|
||||
{{getConfigType(row.type)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="配置信息" prop="config" sortable></el-table-column>
|
||||
<el-table-column label="最后更新时间" prop="updateTime" sortable></el-table-column>
|
||||
<el-table-column label="创建者" prop="creator" sortable width="140"></el-table-column>
|
||||
<el-table-column label="更新者" prop="updater" sortable width="140"></el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template v-slot="{ row }">
|
||||
<el-button v-if="row.isActive === 0" type="primary" size="small" @click="updateConfigStatus(row)">启用</el-button>
|
||||
<el-button type="warning" size="small" @click="handleUpdate(row)">修改</el-button>
|
||||
<el-button type="danger" size="small" @click="handleDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog
|
||||
title="腾讯云存储配置"
|
||||
v-model="dialogVisible"
|
||||
@close="handleCloseDialog"
|
||||
>
|
||||
<!-- 在对话框中输入配置项 -->
|
||||
<el-form :model="formConfig" label-width="120px" :rules="rules">
|
||||
<el-form-item label="存储策略名" prop="name">
|
||||
<el-input v-model="formConfig.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Secret Id" prop="secretId">
|
||||
<el-input v-model="formConfig.secretId"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Secret Key" prop="secretKey">
|
||||
<el-input v-model="formConfig.secretKey"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Region Name" prop="regionName">
|
||||
<el-input v-model="formConfig.regionName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Bucket Name" prop="bucketName">
|
||||
<el-input v-model="formConfig.bucketName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Custom Url">
|
||||
<el-input v-model="formConfig.customUrl"></el-input>
|
||||
</el-form-item>
|
||||
<p> 自2024年1月1日起,腾讯云对象存储新创建的存储桶不支持使用存储桶默认域名(包括存储桶域名、静态网站域名、全球加速域名)在浏览器预览文件,而是直接下载。<a href="https://cloud.tencent.com/document/product/436/96243" target="_blank" referrerpolicy="unsafe-url">了解更多</a></p>
|
||||
<p> 存储桶默认域名存在安全风险,可能暴露敏感信息,建议您为存储桶 配置自定义域名。详情请参见 <a href="https://cloud.tencent.com/document/product/436/102509" target="_blank" referrerpolicy="unsafe-url">存储桶切换自定义域名</a></p>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveCloudConfig">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
title="存储配置修改"
|
||||
v-model="updateDialogVisible"
|
||||
@close="handleCloseUpdateDialog"
|
||||
>
|
||||
<!-- 在对话框中输入配置项 -->
|
||||
<el-form :model="formConfig" label-width="120px" :rules="rules">
|
||||
<el-form-item label="存储策略名" prop="name">
|
||||
<el-input v-model="formConfig.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Secret Id" prop="secretId">
|
||||
<el-input v-model="formConfig.secretId" :disabled="true" @copy.prevent></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Secret Key" prop="secretKey">
|
||||
<el-input v-model="formConfig.secretKey" :disabled="true" @copy.prevent></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Region Name" prop="regionName">
|
||||
<el-input v-model="formConfig.regionName" :disabled="true" @copy.prevent></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Bucket Name" prop="bucketName">
|
||||
<el-input v-model="formConfig.bucketName" :disabled="true" @copy.prevent></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Custom Url">
|
||||
<el-input v-model="formConfig.customUrl"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="updateDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="updateStorageConfig">保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import {BASE_URL} from "@/main";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formConfig: {
|
||||
name: '',
|
||||
secretId: '',
|
||||
secretKey: '',
|
||||
regionName: '',
|
||||
bucketName: '',
|
||||
type: 1,
|
||||
customUrl: ''
|
||||
},
|
||||
configs: [],
|
||||
loading: false, // 加载状态
|
||||
storageType: 1, // 默认选中服务端本地存储
|
||||
dialogVisible: false, // 控制对话框显示隐藏
|
||||
updateDialogVisible: false,// 控制修改对话框显示隐藏
|
||||
updateId: '',//修改配置的id
|
||||
|
||||
rules: {
|
||||
name: [
|
||||
{ required: true, message: '请输入存储策略名', trigger: 'blur' }
|
||||
],
|
||||
secretId: [
|
||||
{ required: true, message: '请输入Secret Id', trigger: 'blur' }
|
||||
],
|
||||
secretKey: [
|
||||
{ required: true, message: '请输入Secret Key', trigger: 'blur' }
|
||||
],
|
||||
regionName: [
|
||||
{ required: true, message: '请输入Region Name', trigger: 'blur' }
|
||||
],
|
||||
bucketName: [
|
||||
{ required: true, message: '请输入Bucket Name', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getStorageConfigs() {
|
||||
this.loading = true;
|
||||
axios.get(BASE_URL+'/oss', {withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.code === 1) {
|
||||
this.configs = response.data.data;
|
||||
} else {
|
||||
ElMessage.error('配置信息获取失败,'+response.data.msg);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('请求失败,'+error);
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
getConfigType(type) {
|
||||
if(type === 0)
|
||||
return '阿里云';
|
||||
else if(type === 1)
|
||||
return '腾讯云';
|
||||
else if(type === 2)
|
||||
return '七牛云';
|
||||
else return '本地';
|
||||
},
|
||||
|
||||
handleChange(val) {
|
||||
if (val === 2) {
|
||||
this.dialogVisible = true; // 显示对话框
|
||||
this.formConfig.type = 1;
|
||||
}
|
||||
},
|
||||
// handleSave() {
|
||||
// if (this.storageType === 2) {
|
||||
// this.saveCloudConfig();
|
||||
// } else {
|
||||
// // 如果选择服务端本地存储,直接保存
|
||||
// this.saveStorageConfig();
|
||||
// }
|
||||
// },
|
||||
handleCloseDialog() {
|
||||
// 关闭对话框时重置配置项
|
||||
this.dialogVisible = false;
|
||||
this.storageType = 1;
|
||||
this.formConfig = {};
|
||||
},
|
||||
handleCloseUpdateDialog() {
|
||||
// 关闭修改对话框时重置配置项
|
||||
this.updateDialogVisible = false;
|
||||
this.formConfig = {};
|
||||
this.updateId = '';
|
||||
},
|
||||
|
||||
handleDelete(row) {
|
||||
ElMessageBox.confirm(
|
||||
'确认要删除该存储配置吗?',
|
||||
'Warning',
|
||||
{
|
||||
confirmButtonText: '是',
|
||||
cancelButtonText: '否',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.deleteConfig(row);
|
||||
})
|
||||
},
|
||||
deleteConfig(row) {
|
||||
axios.delete(BASE_URL+'/oss/'+row.id, {withCredentials:true})
|
||||
.then(response => {
|
||||
if(response.data.code === 1){
|
||||
ElMessage.success(response.data.data);
|
||||
this.getStorageConfigs();
|
||||
} else {
|
||||
ElMessage.error('存储配置 '+row.name+' 删除失败,'+response.data.msg);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('请求失败:'+error);
|
||||
});
|
||||
},
|
||||
|
||||
handleUpdate(row) {
|
||||
this.updateId = row.id;
|
||||
this.formConfig.name = row.name;
|
||||
if(row.config !== null) {
|
||||
let config = JSON.parse(row.config);
|
||||
this.formConfig.secretId = config.secretId;
|
||||
this.formConfig.secretKey = config.secretKey;
|
||||
this.formConfig.regionName = config.regionName;
|
||||
this.formConfig.bucketName = config.bucketName;
|
||||
this.formConfig.customUrl = config.customUrl;
|
||||
}
|
||||
this.updateDialogVisible = true;
|
||||
},
|
||||
updateStorageConfig() {
|
||||
// 发送云存储配置到后端保存
|
||||
axios.put(BASE_URL+'/oss/'+this.updateId, this.formConfig, {withCredentials: true})
|
||||
.then(response => {
|
||||
if(response.data.code === 1) {
|
||||
ElMessage.success(response.data.data);
|
||||
this.getStorageConfigs();
|
||||
} else {
|
||||
ElMessage.error('修改存储配置失败,'+response.data.msg);
|
||||
}
|
||||
this.handleCloseUpdateDialog();
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('请求失败,'+error);
|
||||
});
|
||||
},
|
||||
|
||||
updateConfigStatus(row) {
|
||||
axios.put(BASE_URL+'/oss/'+row.id+'/'+row.isActive, {}, {withCredentials: true})
|
||||
.then(response => {
|
||||
if(response.data.code === 1){
|
||||
this.getStorageConfigs();
|
||||
} else {
|
||||
ElMessage.error('修改存储配置失败,'+response.data.msg);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('请求失败,'+error);
|
||||
});
|
||||
},
|
||||
|
||||
saveCloudConfig() {
|
||||
// 发送云存储配置到后端保存
|
||||
axios.post(BASE_URL+'/oss', this.formConfig, {withCredentials: true})
|
||||
.then(response => {
|
||||
if(response.data.code === 1) {
|
||||
ElMessage.success(response.data.data);
|
||||
this.getStorageConfigs();
|
||||
} else {
|
||||
ElMessage.error('保存云存储配置失败,'+response.data.msg);
|
||||
}
|
||||
this.handleCloseDialog();
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('请求失败,'+error);
|
||||
});
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getStorageConfigs();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
176
src/components/ProfilePage.vue
Normal file
176
src/components/ProfilePage.vue
Normal file
@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<!-- 个人中心内容 -->
|
||||
<div>
|
||||
<h3 style="text-align: left">个人信息</h3>
|
||||
<p style="text-align: left">用户名:{{ currentUser.username }}</p>
|
||||
<p style="text-align: left">邮箱:{{ currentUser.email }}</p>
|
||||
<p style="text-align: left">当前使用存储空间:{{getSize(this.currentUser.storageUsed) + " / " + getSize(this.currentUser.storageTotal)}} </p>
|
||||
</div>
|
||||
<!-- 修改密码表单 -->
|
||||
<div>
|
||||
<h3 style="text-align: left">修改密码</h3>
|
||||
<el-form ref="passwordForm" :model="passwordForm" :rules="rules" label-width="80px" style="max-width: 400px;">
|
||||
<el-form-item label="旧密码" prop="oldPassword" >
|
||||
<el-input v-model="passwordForm.oldPassword" type="password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input v-model="passwordForm.newPassword" type="password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input v-model="passwordForm.confirmPassword" type="password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="updatePassword">提交</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 修改用户名表单 -->
|
||||
<div>
|
||||
<h3 style="text-align: left">修改用户名</h3>
|
||||
<el-form ref="usernameForm" :rules="rules" :model="usernameForm" label-width="80px" style="max-width: 400px;">
|
||||
<el-form-item label="新用户名" prop="username">
|
||||
<el-input v-model="usernameForm.username"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="updateUsername">提交</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ElMessage} from "element-plus";
|
||||
import axios from "axios";
|
||||
import {BASE_URL} from "@/main";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
currentUser: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
passwordForm: {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
},
|
||||
usernameForm: {
|
||||
username: ''
|
||||
},
|
||||
|
||||
rules: {
|
||||
username: [{ required: true, message: '请输入新用户名', trigger: 'blur' }],
|
||||
oldPassword: [{ required: true, message: '请输入旧密码', trigger: 'blur' }],
|
||||
newPassword: [{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === this.passwordForm.oldPassword) {
|
||||
callback(new Error('新密码不能与旧密码相同'));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
confirmPassword: [{ required: true, message: '请确认密码', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === this.passwordForm.newPassword) {
|
||||
callback();
|
||||
} else {
|
||||
callback(new Error('两次输入的密码不一致'));
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getSize(size) {
|
||||
if(size/1024 < 1.0)
|
||||
return size.toFixed(1)+'B';
|
||||
if(size/(1024*1024) < 1.0)
|
||||
return (size/(1024)).toFixed(1)+'KB';
|
||||
if(size/(1024*1024*1024) < 1.0)
|
||||
return (size/(1024*1024)).toFixed(1)+'MB';
|
||||
return (size/(1024*1024*1024)).toFixed(1)+'GB';
|
||||
},
|
||||
|
||||
updatePassword() {
|
||||
if (!this.passwordForm.oldPassword || !this.passwordForm.newPassword || !this.passwordForm.newPassword) {
|
||||
ElMessage.error('请填写必填项');
|
||||
return;
|
||||
}
|
||||
if(this.passwordForm.newPassword !== this.passwordForm.confirmPassword){
|
||||
ElMessage.error('两次输入的密码不一致');
|
||||
return;
|
||||
}
|
||||
if(this.passwordForm.newPassword === this.passwordForm.oldPassword){
|
||||
ElMessage.error('新密码不能与旧密码相同');
|
||||
return;
|
||||
}
|
||||
// 发送修改密码请求
|
||||
axios.put(BASE_URL + '/user/updatePassword', {
|
||||
oldPassword: this.passwordForm.oldPassword,
|
||||
newPassword: this.passwordForm.newPassword
|
||||
}, { withCredentials: true })
|
||||
.then(response => {
|
||||
if(response.data.code === 1) {
|
||||
ElMessage.success(response.data.data);
|
||||
// 清空表单
|
||||
this.passwordForm = {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
};
|
||||
this.$router.push({name: 'Login'});
|
||||
} else {
|
||||
ElMessage.error('密码修改失败,'+response.data.msg);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('请求失败:'+ error);
|
||||
});
|
||||
},
|
||||
updateUsername() {
|
||||
this.$refs.usernameForm.validate(valid => {
|
||||
if (valid) {
|
||||
// 发送修改用户名请求
|
||||
axios.put(BASE_URL + '/user/updateUsername', {
|
||||
username: this.usernameForm.username
|
||||
}, { withCredentials: true })
|
||||
.then(response => {
|
||||
if(response.data.code === 1) {
|
||||
ElMessage.success(response.data.data);
|
||||
// 更新当前用户信息
|
||||
this.$emit('update:currentUser', this.usernameForm.username);
|
||||
// this.currentUser.username = this.usernameForm.username;
|
||||
// 清空表单
|
||||
this.usernameForm = {
|
||||
username: ''
|
||||
};
|
||||
} else {
|
||||
ElMessage.error('用户名修改失败,'+response.data.msg);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('请求失败,'+error);
|
||||
});
|
||||
} else ElMessage.error('请输入新用户名');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
259
src/components/RecycleBinPage.vue
Normal file
259
src/components/RecycleBinPage.vue
Normal file
@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<el-row>
|
||||
<el-col :span="18">
|
||||
<el-form ref="searchForm" :model="searchForm" >
|
||||
<el-row>
|
||||
<el-input v-model="searchForm.name" placeholder="请输入要查询的文件名" style="width: 240px; padding-right: 10px"></el-input>
|
||||
<el-button type="primary" @click="getFiles" :icon="Search"></el-button>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<!-- 批量操作按钮 -->
|
||||
<el-button-group>
|
||||
<el-button type="primary" @click="handleBatchRestore">批量恢复</el-button>
|
||||
<el-button type="danger" @click="handleBatchDelete">批量删除</el-button>
|
||||
</el-button-group>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 文件列表 -->
|
||||
<el-table
|
||||
:data="files"
|
||||
border
|
||||
:v-loading="loading"
|
||||
:default-sort="{ prop: 'name', order: 'ascending' }"
|
||||
@selection-change="handleSelectionChange"
|
||||
:show-overflow-tooltip=true
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column label="序号" type="index" />
|
||||
<el-table-column label="文件名" prop="name" sortable></el-table-column>
|
||||
<el-table-column label="文件大小" prop="size" sortable>
|
||||
<template v-slot="{ row }">
|
||||
{{getFileSize(row.size)}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" label="文件类型" sortable>
|
||||
<template v-slot="{ row }">
|
||||
{{ getFileType(row.type) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最后更新时间" prop="updateTime" sortable></el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template v-slot="{ row }">
|
||||
<el-button type="primary" size="small" @click="restoreFile(row)">恢复</el-button>
|
||||
<el-button type="danger" size="small" @click="handleDelete(row)">彻底删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:page-sizes="[5, 10, 20, 50]"
|
||||
:total="total"
|
||||
:background="true"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange">
|
||||
</el-pagination>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ElMessage, ElMessageBox} from "element-plus";
|
||||
import axios from "axios";
|
||||
import {BASE_URL} from "@/main";
|
||||
import {Search} from "@element-plus/icons-vue";
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
Search() {
|
||||
return Search
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
|
||||
selectedRows: [], // 存储选中的行数据
|
||||
searchForm: {name: ''},
|
||||
files: [], // 所有文件数据
|
||||
loading: false, // 加载状态
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理每页显示条数变化
|
||||
handleSizeChange(size) {
|
||||
this.pageSize = size;
|
||||
this.currentPage = 1; // 重置当前页码为1
|
||||
this.getFiles();
|
||||
},
|
||||
// 处理当前页码变化
|
||||
handleCurrentChange(page) {
|
||||
this.currentPage = page;
|
||||
this.getFiles();
|
||||
},
|
||||
|
||||
// 多选框选中状态变化时
|
||||
handleSelectionChange(selection) {
|
||||
this.selectedRows = selection; // 更新选中的行数据
|
||||
},
|
||||
|
||||
handleBatchRestore() {
|
||||
let ids = [];
|
||||
this.selectedRows.forEach(item => {
|
||||
ids.push(item.id);
|
||||
});
|
||||
axios.put(BASE_URL+'/file'+'/0', ids, {withCredentials: true})
|
||||
.then(response => {
|
||||
if(response.data.code === 1){
|
||||
ElMessage.success(response.data.data);
|
||||
} else {
|
||||
ElMessage.error('文件批量恢复失败,'+response.data.msg);
|
||||
}
|
||||
this.getFiles();
|
||||
})
|
||||
.catch(error => {
|
||||
//console.log(error);
|
||||
ElMessage.error('请求失败:'+error);
|
||||
});
|
||||
},
|
||||
|
||||
handleBatchDelete() {
|
||||
ElMessageBox.confirm(
|
||||
'确认要彻底删除这些文件吗?',
|
||||
'Warning',
|
||||
{
|
||||
confirmButtonText: '是',
|
||||
cancelButtonText: '否',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.batchPermanentDeleteFile();
|
||||
})
|
||||
},
|
||||
handleDelete(file) {
|
||||
ElMessageBox.confirm(
|
||||
'确认要彻底删除该文件吗?',
|
||||
'Warning',
|
||||
{
|
||||
confirmButtonText: '是',
|
||||
cancelButtonText: '否',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.permanentDeleteFile(file);
|
||||
})
|
||||
},
|
||||
|
||||
// 获取文件列表数据
|
||||
getFiles() {
|
||||
this.loading = true; // 显示加载状态
|
||||
let url = BASE_URL + '/file/page/'+this.currentPage+'/'+this.pageSize+'/1';
|
||||
axios.get(url, {params: {name: ''}, withCredentials: true})
|
||||
.then(response => {
|
||||
if (response.data.code === 1) {
|
||||
// this.files = response.data.data; // 设置文件列表数据
|
||||
this.files = response.data.data.records; // 设置文件列表数据
|
||||
this.total = response.data.data.total;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
//console.error('文件列表获取失败', error);
|
||||
ElMessage.error('文件列表获取失败'+error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false; // 隐藏加载状态
|
||||
});
|
||||
},
|
||||
getFileSize(size) {
|
||||
if(size/1024 < 1.0)
|
||||
return size.toFixed(1)+'B';
|
||||
if(size/(1024*1024) < 1.0)
|
||||
return (size/(1024)).toFixed(1)+'KB';
|
||||
if(size/(1024*1024*1024) < 1.0)
|
||||
return (size/(1024*1024)).toFixed(1)+'MB';
|
||||
if(size/(1024*1024*1024*1024) < 1.0)
|
||||
return (size/(1024*1024*1024)).toFixed(1)+'GB';
|
||||
},
|
||||
getFileType(type) {
|
||||
// 根据文件类型代码返回对应的文字描述
|
||||
switch (type) {
|
||||
case 0:
|
||||
return '图片';
|
||||
case 1:
|
||||
return '视频';
|
||||
case 2:
|
||||
return '音频';
|
||||
case 3:
|
||||
return '文档';
|
||||
default:
|
||||
return '其他';
|
||||
}
|
||||
},
|
||||
restoreFile(file) {
|
||||
// 恢复文件
|
||||
//console.log('恢复文件:', file);
|
||||
axios.put(BASE_URL+'/file/'+file.id+'/0', {}, {withCredentials: true})
|
||||
.then(response => {
|
||||
if(response.data.code === 0){
|
||||
ElMessage.error('文件'+file.name+'恢复失败,'+response.data.msg);
|
||||
} else {
|
||||
ElMessage.success(response.data.data);
|
||||
}
|
||||
this.getFiles();
|
||||
}).catch(error => {
|
||||
ElMessage.error("请求失败:"+error);
|
||||
});
|
||||
},
|
||||
permanentDeleteFile(file) {
|
||||
// 删除文件的操作
|
||||
//console.log('删除文件:', file);
|
||||
axios.delete(BASE_URL+'/file/'+file.id, {withCredentials: true})
|
||||
.then(response => {
|
||||
if(response.data.code === 1){
|
||||
ElMessage.success(response.data.data);
|
||||
} else {
|
||||
ElMessage.error('文件'+file.name+'删除失败,'+response.data.msg);
|
||||
}
|
||||
this.getFiles();
|
||||
})
|
||||
.catch(error => {
|
||||
//console.log(error);
|
||||
ElMessage.error('请求失败:'+error);
|
||||
});
|
||||
},
|
||||
batchPermanentDeleteFile() {
|
||||
let ids = [];
|
||||
this.selectedRows.forEach(item => {
|
||||
ids.push(item.id);
|
||||
});
|
||||
axios.delete(BASE_URL+'/file', {data: ids, withCredentials: true})
|
||||
.then(response => {
|
||||
if(response.data.code === 1){
|
||||
ElMessage.success(response.data.data);
|
||||
} else {
|
||||
ElMessage.error('文件批量删除失败,'+response.data.msg);
|
||||
}
|
||||
this.getFiles();
|
||||
})
|
||||
.catch(error => {
|
||||
//console.log(error);
|
||||
ElMessage.error('请求失败:'+error);
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getFiles();
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
176
src/components/RegistryPage.vue
Normal file
176
src/components/RegistryPage.vue
Normal file
@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="background"></div>
|
||||
<div class="content">
|
||||
<h1 class="welcome-title">欢迎注册文件管理系统</h1>
|
||||
<el-form :model="registerForm" :rules="rules" label-width="100px">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="registerForm.email" placeholder="请输入邮箱"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input type="password" v-model="registerForm.password" placeholder="请输入密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input type="password" v-model="registerForm.confirmPassword" placeholder="请确认密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="验证码" prop="verificationCode">
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<el-input v-model="registerForm.verificationCode" placeholder="请输入验证码"></el-input>
|
||||
</el-col>
|
||||
<el-col :span="8" style="padding-left: 10px">
|
||||
<el-button type="primary" @click="sendVerificationCode" :disabled="sendingVerificationCode">{{ sendButtonLabel }}</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="register">注册</el-button>
|
||||
<el-button @click="goToLogin" plain>返回登录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import { BASE_URL } from "@/main";
|
||||
import { ElForm, ElFormItem, ElInput, ElButton, ElMessage, ElRow, ElCol } from 'element-plus';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElButton,
|
||||
ElRow,
|
||||
ElCol
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
registerForm: {
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
verificationCode: ''
|
||||
},
|
||||
rules: {
|
||||
email: [{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur'}],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
confirmPassword: [{ required: true, message: '请确认密码', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value === this.registerForm.password) {
|
||||
callback();
|
||||
} else {
|
||||
callback(new Error('两次输入的密码不一致'));
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
verificationCode: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
|
||||
},
|
||||
sendingVerificationCode: false,
|
||||
sendButtonLabel: '获取验证码'
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
sendVerificationCode() {
|
||||
if (!this.registerForm.email) {
|
||||
ElMessage.error('请填写邮箱');
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用正则表达式来验证邮箱格式是否正确
|
||||
const emailPattern = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
|
||||
if (!emailPattern.test(this.registerForm.email)) {
|
||||
ElMessage.error('请输入有效的邮箱地址');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.sendingVerificationCode) return;
|
||||
|
||||
this.sendingVerificationCode = true;
|
||||
this.sendButtonLabel = '发送中...';
|
||||
|
||||
axios.post(BASE_URL + '/user/sendVerCode', {email: this.registerForm.email})
|
||||
.then(response => {
|
||||
if (response.data.code === 1) {
|
||||
ElMessage.success(response.data.data);
|
||||
//console.log('验证码发送成功', response.data);
|
||||
} else {
|
||||
ElMessage.error(response.data.msg);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('请求失败,' + error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.sendingVerificationCode = false;
|
||||
this.sendButtonLabel = '获取验证码';
|
||||
});
|
||||
},
|
||||
register() {
|
||||
if(!this.registerForm.email || !this.registerForm.password || !this.registerForm.confirmPassword || !this.registerForm.verificationCode){
|
||||
ElMessage.error('请填写必填项');
|
||||
return;
|
||||
}
|
||||
if(this.registerForm.password !== this.registerForm.confirmPassword){
|
||||
ElMessage.error('两次输入的密码不一致');
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log('注册', this.registerForm);
|
||||
axios.post(BASE_URL + '/user/registry', this.registerForm)
|
||||
.then(response => {
|
||||
if (response.data.code === 1) {
|
||||
//console.log('注册成功', response.data);
|
||||
ElMessage.success(response.data.data);
|
||||
this.$router.push({name:'Login'});
|
||||
} else if (response.data.code === 0) {
|
||||
//console.log('注册失败', response.data);
|
||||
ElMessage.error('注册失败,' + response.data.msg)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
ElMessage.error('请求失败,'+error);
|
||||
});
|
||||
},
|
||||
goToLogin() {
|
||||
//console.log('跳转到登录页面');
|
||||
this.$router.push({name:'Login'});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('/public/background.png');
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 400px;
|
||||
padding: 20px;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
30
src/main.js
Normal file
30
src/main.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { createApp } from 'vue'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import router from './router/router.js'
|
||||
import App from './App.vue'
|
||||
|
||||
export const BASE_URL = 'http://localhost:8080';
|
||||
|
||||
createApp(App).use(router).use(ElementPlus).mount('#app')
|
||||
|
||||
const debounce = (fn, delay) => {
|
||||
let timer = null;
|
||||
return function () {
|
||||
let context = this;
|
||||
let args = arguments;
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function () {
|
||||
fn.apply(context, args);
|
||||
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
|
||||
const _ResizeObserver = window.ResizeObserver;
|
||||
window.ResizeObserver = class ResizeObserver extends _ResizeObserver {
|
||||
constructor(callback) {
|
||||
callback = debounce(callback, 16);
|
||||
super(callback);
|
||||
}
|
||||
}
|
46
src/router/router.js
Normal file
46
src/router/router.js
Normal file
@ -0,0 +1,46 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import LoginPage from '@/components/LoginPage.vue';
|
||||
import RegistryPage from '@/components/RegistryPage.vue';
|
||||
import HomePage from '@/components/HomePage.vue'
|
||||
import {ElMessage} from "element-plus";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Login',
|
||||
component: LoginPage
|
||||
},
|
||||
{
|
||||
path: '/registry',
|
||||
name: 'Registry',
|
||||
component: RegistryPage
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
name: 'Home',
|
||||
component: HomePage,
|
||||
meta: {requiresAuth: true}
|
||||
}
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
routes
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
//console.log(document.cookie);
|
||||
const isAuthenticated = document.cookie.includes('user'); // 检查 cookie 中是否包含 user
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (!isAuthenticated) {
|
||||
ElMessage.error('尚未登录,请勿非法访问');
|
||||
next({ path: '/' }); // 如果没有登录,则跳转到登录页面
|
||||
} else {
|
||||
next(); // 已登录,继续访问目标页面
|
||||
}
|
||||
} else {
|
||||
next(); // 不需要验证登录,直接访问
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
17
vue.config.js
Normal file
17
vue.config.js
Normal file
@ -0,0 +1,17 @@
|
||||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true,
|
||||
lintOnSave: false,
|
||||
devServer: {
|
||||
port: 8088,
|
||||
client: {
|
||||
overlay: false
|
||||
}
|
||||
},
|
||||
chainWebpack: (config) => {
|
||||
config.plugin('define').tap((definitions) => {
|
||||
definitions[0].__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = false;
|
||||
return definitions;
|
||||
});
|
||||
}
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user