init
@@ -0,0 +1,49 @@
|
||||
# ==================
|
||||
# IDE & Editor
|
||||
# ==================
|
||||
.idea/
|
||||
.vscode/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
# ==================
|
||||
# OS
|
||||
# ==================
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# ==================
|
||||
# Java (easypan-java)
|
||||
# ==================
|
||||
HELP.md
|
||||
target/
|
||||
build/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
# ==================
|
||||
# Frontend (easypan-front)
|
||||
# ==================
|
||||
node_modules/
|
||||
easypan-front/dist/
|
||||
|
||||
# ==================
|
||||
# Logs
|
||||
# ==================
|
||||
*.log
|
||||
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<script src="/hls.min.js"></script>
|
||||
<title>SmallPan</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "easypan-front-web",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite --mode dev",
|
||||
"build": "vite build --mode build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@highlightjs/vue-plugin": "^2.1.0",
|
||||
"@moefe/vue-aplayer": "^2.0.0-beta.5",
|
||||
"aplayer": "^1.10.1",
|
||||
"axios": "^1.3.4",
|
||||
"docx-preview": "^0.1.15",
|
||||
"dplayer": "^1.27.1",
|
||||
"element-plus": "^2.2.36",
|
||||
"highlight.js": "^11.7.0",
|
||||
"hls.js": "^1.1.5",
|
||||
"js-md5": "^0.7.3",
|
||||
"pinia": "^2.0.32",
|
||||
"sass": "^1.59.2",
|
||||
"sass-loader": "^13.2.0",
|
||||
"spark-md5": "^3.0.2",
|
||||
"vue": "^3.2.47",
|
||||
"vue-clipboard3": "^2.0.0",
|
||||
"vue-cookies": "^1.8.3",
|
||||
"vue-pdf-embed": "^1.1.5",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue3-pdfjs": "^0.1.6",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.0.0",
|
||||
"vite": "^4.1.4"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<el-config-provider :locale="locale" :message="config">
|
||||
<router-view></router-view>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from "vue";
|
||||
import framework from "@/views/Framework.vue";
|
||||
import zhCn from "element-plus/lib/locale/lang/zh-cn";
|
||||
const locale = zhCn;
|
||||
const config = reactive({
|
||||
max: 1,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,65 @@
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgb(239, 239, 239);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #bfbfbf;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #bfbfbf;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: #bfbfbf;
|
||||
}
|
||||
|
||||
:root {
|
||||
--link: #007fff;
|
||||
--pink: #FF6699;
|
||||
--text2: #61666d;
|
||||
--icon: #9499a0;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
font-size: 14px;
|
||||
color: #636d7e;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.a-link {
|
||||
text-decoration: none;
|
||||
color: var(--link);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container-body {
|
||||
margin: 0px auto;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
.iconfont::before {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
.top {
|
||||
margin-top: 20px;
|
||||
|
||||
.top-op {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.btn {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.search-panel {
|
||||
margin-left: 10px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.icon-refresh {
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.not-allow {
|
||||
background: #d2d2d2 !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-list {
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 6px 0px;
|
||||
|
||||
.file-name {
|
||||
margin-left: 8px;
|
||||
flex: 1;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #06a7ff;
|
||||
}
|
||||
}
|
||||
|
||||
.transfer-status {
|
||||
font-size: 13px;
|
||||
margin-left: 10px;
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.transfer-fail {
|
||||
color: #f75000;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-panel {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0px 5px;
|
||||
|
||||
.iconfont {
|
||||
margin-left: 10px;
|
||||
background: #0c95f7;
|
||||
color: #fff;
|
||||
padding: 3px 5px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.not-allow {
|
||||
cursor: not-allowed;
|
||||
background: #7cb1d7;
|
||||
color: #ddd;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.op {
|
||||
width: 280px;
|
||||
margin-left: 15px;
|
||||
|
||||
.iconfont {
|
||||
font-size: 13px;
|
||||
margin-left: 10px;
|
||||
color: #06a7ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.iconfont::before {
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-data {
|
||||
height: calc(100vh - 150px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.no-data-inner {
|
||||
text-align: center;
|
||||
|
||||
.tips {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.op-list {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.op-item {
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin: 0px 10px;
|
||||
padding: 5px 0px;
|
||||
background: rgb(241, 241, 241);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 995 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 912 B |
|
After Width: | Height: | Size: 887 B |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 768 B |
|
After Width: | Height: | Size: 884 B |
|
After Width: | Height: | Size: 852 B |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 722 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 802 B |
|
After Width: | Height: | Size: 840 B |
|
After Width: | Height: | Size: 727 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 711 B |
|
After Width: | Height: | Size: 863 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 995 B |
|
After Width: | Height: | Size: 912 B |
|
After Width: | Height: | Size: 887 B |
|
After Width: | Height: | Size: 768 B |
|
After Width: | Height: | Size: 884 B |
|
After Width: | Height: | Size: 852 B |
|
After Width: | Height: | Size: 734 B |
|
After Width: | Height: | Size: 802 B |
|
After Width: | Height: | Size: 840 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 727 B |
|
After Width: | Height: | Size: 711 B |
|
After Width: | Height: | Size: 863 B |
|
After Width: | Height: | Size: 535 B |
@@ -0,0 +1,539 @@
|
||||
/* Logo 字体 */
|
||||
@font-face {
|
||||
font-family: "iconfont logo";
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: "iconfont logo";
|
||||
font-size: 160px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* tabs */
|
||||
.nav-tabs {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-more {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#tabs {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#tabs li {
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
border-bottom: 2px solid transparent;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-bottom: -1px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
||||
#tabs .active {
|
||||
border-bottom-color: #f00;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.tab-container .content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 页面布局 */
|
||||
.main {
|
||||
padding: 30px 100px;
|
||||
width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.main .logo {
|
||||
color: #333;
|
||||
text-align: left;
|
||||
margin-bottom: 30px;
|
||||
line-height: 1;
|
||||
height: 110px;
|
||||
margin-top: -50px;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.main .logo a {
|
||||
font-size: 160px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.helps {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.helps pre {
|
||||
padding: 20px;
|
||||
margin: 10px 0;
|
||||
border: solid 1px #e7e1cd;
|
||||
background-color: #fffdef;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.icon_lists {
|
||||
width: 100% !important;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.icon_lists li {
|
||||
width: 100px;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 20px;
|
||||
text-align: center;
|
||||
list-style: none !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.icon_lists li .code-name {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.icon_lists .icon {
|
||||
display: block;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
font-size: 42px;
|
||||
margin: 10px auto;
|
||||
color: #333;
|
||||
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
|
||||
-moz-transition: font-size 0.25s linear, width 0.25s linear;
|
||||
transition: font-size 0.25s linear, width 0.25s linear;
|
||||
}
|
||||
|
||||
.icon_lists .icon:hover {
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
.icon_lists .svg-icon {
|
||||
/* 通过设置 font-size 来改变图标大小 */
|
||||
width: 1em;
|
||||
/* 图标和文字相邻时,垂直对齐 */
|
||||
vertical-align: -0.15em;
|
||||
/* 通过设置 color 来改变 SVG 的颜色/fill */
|
||||
fill: currentColor;
|
||||
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
|
||||
normalize.css 中也包含这行 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon_lists li .name,
|
||||
.icon_lists li .code-name {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* markdown 样式 */
|
||||
.markdown {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.markdown img {
|
||||
vertical-align: middle;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
color: #404040;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown h2,
|
||||
.markdown h3,
|
||||
.markdown h4,
|
||||
.markdown h5,
|
||||
.markdown h6 {
|
||||
color: #404040;
|
||||
margin: 1.6em 0 0.6em 0;
|
||||
font-weight: 500;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.markdown h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.markdown h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.markdown h4 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.markdown h5 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown h6 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown hr {
|
||||
height: 1px;
|
||||
border: 0;
|
||||
background: #e9e9e9;
|
||||
margin: 16px 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown>p,
|
||||
.markdown>blockquote,
|
||||
.markdown>.highlight,
|
||||
.markdown>ol,
|
||||
.markdown>ul {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.markdown ul>li {
|
||||
list-style: circle;
|
||||
}
|
||||
|
||||
.markdown>ul li,
|
||||
.markdown blockquote ul>li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown>ul li p,
|
||||
.markdown>ol li p {
|
||||
margin: 0.6em 0;
|
||||
}
|
||||
|
||||
.markdown ol>li {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.markdown>ol li,
|
||||
.markdown blockquote ol>li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown code {
|
||||
margin: 0 3px;
|
||||
padding: 0 5px;
|
||||
background: #eee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.markdown strong,
|
||||
.markdown b {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown>table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0px;
|
||||
empty-cells: show;
|
||||
border: 1px solid #e9e9e9;
|
||||
width: 95%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown>table th {
|
||||
white-space: nowrap;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown>table th,
|
||||
.markdown>table td {
|
||||
border: 1px solid #e9e9e9;
|
||||
padding: 8px 16px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown>table th {
|
||||
background: #F7F7F7;
|
||||
}
|
||||
|
||||
.markdown blockquote {
|
||||
font-size: 90%;
|
||||
color: #999;
|
||||
border-left: 4px solid #e9e9e9;
|
||||
padding-left: 0.8em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown blockquote p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown .anchor {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.markdown .waiting {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.markdown h1:hover .anchor,
|
||||
.markdown h2:hover .anchor,
|
||||
.markdown h3:hover .anchor,
|
||||
.markdown h4:hover .anchor,
|
||||
.markdown h5:hover .anchor,
|
||||
.markdown h6:hover .anchor {
|
||||
opacity: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.markdown>br,
|
||||
.markdown>p>br {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
background: white;
|
||||
padding: 0.5em;
|
||||
color: #333333;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-meta {
|
||||
color: #969896;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-strong,
|
||||
.hljs-emphasis,
|
||||
.hljs-quote {
|
||||
color: #df5000;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-type {
|
||||
color: #a71d5d;
|
||||
}
|
||||
|
||||
.hljs-literal,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-attribute {
|
||||
color: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-name {
|
||||
color: #63a35c;
|
||||
}
|
||||
|
||||
.hljs-tag {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-attr,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #795da3;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #55a532;
|
||||
background-color: #eaffea;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #bd2c00;
|
||||
background-color: #ffecec;
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 代码高亮 */
|
||||
/* PrismJS 1.15.0
|
||||
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
background: none;
|
||||
text-shadow: 0 1px white;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection,
|
||||
pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre)>code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre)>code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #9a6e3a;
|
||||
background: hsla(0, 0%, 100%, .5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #DD4A68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3946741 */
|
||||
src: url('iconfont.woff2?t=1680443314592') format('woff2'),
|
||||
url('iconfont.woff?t=1680443314592') format('woff'),
|
||||
url('iconfont.ttf?t=1680443314592') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-settings:before {
|
||||
content: "\e673";
|
||||
}
|
||||
|
||||
.icon-del:before {
|
||||
content: "\e636";
|
||||
}
|
||||
|
||||
.icon-checkcode:before {
|
||||
content: "\e626";
|
||||
}
|
||||
|
||||
.icon-account:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.icon-password:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.icon-doc:before {
|
||||
content: "\e882";
|
||||
}
|
||||
|
||||
.icon-import:before {
|
||||
content: "\e60e";
|
||||
}
|
||||
|
||||
.icon-link:before {
|
||||
content: "\e600";
|
||||
}
|
||||
|
||||
.icon-cancel:before {
|
||||
content: "\e772";
|
||||
}
|
||||
|
||||
.icon-revert:before {
|
||||
content: "\e60f";
|
||||
}
|
||||
|
||||
.icon-close3:before {
|
||||
content: "\e658";
|
||||
}
|
||||
|
||||
.icon-search:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.icon-transfer:before {
|
||||
content: "\e6b4";
|
||||
}
|
||||
|
||||
.icon-refresh:before {
|
||||
content: "\e6a2";
|
||||
}
|
||||
|
||||
.icon-close2:before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.icon-start:before {
|
||||
content: "\e625";
|
||||
}
|
||||
|
||||
.icon-pause:before {
|
||||
content: "\e629";
|
||||
}
|
||||
|
||||
.icon-ok:before {
|
||||
content: "\e613";
|
||||
}
|
||||
|
||||
.icon-clock:before {
|
||||
content: "\e7d7";
|
||||
}
|
||||
|
||||
.icon-close:before {
|
||||
content: "\e656";
|
||||
}
|
||||
|
||||
.icon-share:before {
|
||||
content: "\e65e";
|
||||
}
|
||||
|
||||
.icon-right1:before {
|
||||
content: "\e65b";
|
||||
}
|
||||
|
||||
.icon-error:before {
|
||||
content: "\e651";
|
||||
}
|
||||
|
||||
.icon-edit:before {
|
||||
content: "\e61f";
|
||||
}
|
||||
|
||||
.icon-share1:before {
|
||||
content: "\e86f";
|
||||
}
|
||||
|
||||
.icon-move:before {
|
||||
content: "\e67b";
|
||||
}
|
||||
|
||||
.icon-folder-add:before {
|
||||
content: "\e7d1";
|
||||
}
|
||||
|
||||
.icon-right:before {
|
||||
content: "\e7eb";
|
||||
}
|
||||
|
||||
.icon-download:before {
|
||||
content: "\e83a";
|
||||
}
|
||||
|
||||
.icon-upload:before {
|
||||
content: "\e83b";
|
||||
}
|
||||
|
||||
.icon-all:before {
|
||||
content: "\e6ff";
|
||||
}
|
||||
|
||||
.icon-cloude:before {
|
||||
content: "\e66d";
|
||||
}
|
||||
|
||||
.icon-movie2:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
|
||||
.icon-more:before {
|
||||
content: "\e631";
|
||||
}
|
||||
|
||||
.icon-music:before {
|
||||
content: "\e6a1";
|
||||
}
|
||||
|
||||
.icon-video:before {
|
||||
content: "\e74f";
|
||||
}
|
||||
|
||||
.icon-image:before {
|
||||
content: "\e68d";
|
||||
}
|
||||
|
||||
.icon-pan:before {
|
||||
content: "\e6e2";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
{
|
||||
"id": "3946741",
|
||||
"name": "easypan",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "715362",
|
||||
"name": "settings",
|
||||
"font_class": "settings",
|
||||
"unicode": "e673",
|
||||
"unicode_decimal": 58995
|
||||
},
|
||||
{
|
||||
"icon_id": "1440893",
|
||||
"name": "回收站",
|
||||
"font_class": "del",
|
||||
"unicode": "e636",
|
||||
"unicode_decimal": 58934
|
||||
},
|
||||
{
|
||||
"icon_id": "553324",
|
||||
"name": "验证 验证码",
|
||||
"font_class": "checkcode",
|
||||
"unicode": "e626",
|
||||
"unicode_decimal": 58918
|
||||
},
|
||||
{
|
||||
"icon_id": "2127118",
|
||||
"name": "账户",
|
||||
"font_class": "account",
|
||||
"unicode": "e60a",
|
||||
"unicode_decimal": 58890
|
||||
},
|
||||
{
|
||||
"icon_id": "12779504",
|
||||
"name": "password",
|
||||
"font_class": "password",
|
||||
"unicode": "e65d",
|
||||
"unicode_decimal": 58973
|
||||
},
|
||||
{
|
||||
"icon_id": "9626991",
|
||||
"name": "文档",
|
||||
"font_class": "doc",
|
||||
"unicode": "e882",
|
||||
"unicode_decimal": 59522
|
||||
},
|
||||
{
|
||||
"icon_id": "10885271",
|
||||
"name": "导入",
|
||||
"font_class": "import",
|
||||
"unicode": "e60e",
|
||||
"unicode_decimal": 58894
|
||||
},
|
||||
{
|
||||
"icon_id": "9223527",
|
||||
"name": "link",
|
||||
"font_class": "link",
|
||||
"unicode": "e600",
|
||||
"unicode_decimal": 58880
|
||||
},
|
||||
{
|
||||
"icon_id": "26867547",
|
||||
"name": "cancel",
|
||||
"font_class": "cancel",
|
||||
"unicode": "e772",
|
||||
"unicode_decimal": 59250
|
||||
},
|
||||
{
|
||||
"icon_id": "21189968",
|
||||
"name": "还原",
|
||||
"font_class": "revert",
|
||||
"unicode": "e60f",
|
||||
"unicode_decimal": 58895
|
||||
},
|
||||
{
|
||||
"icon_id": "29943",
|
||||
"name": "round_close_fill",
|
||||
"font_class": "close3",
|
||||
"unicode": "e658",
|
||||
"unicode_decimal": 58968
|
||||
},
|
||||
{
|
||||
"icon_id": "29947",
|
||||
"name": "search",
|
||||
"font_class": "search",
|
||||
"unicode": "e65c",
|
||||
"unicode_decimal": 58972
|
||||
},
|
||||
{
|
||||
"icon_id": "15066965",
|
||||
"name": "transfer",
|
||||
"font_class": "transfer",
|
||||
"unicode": "e6b4",
|
||||
"unicode_decimal": 59060
|
||||
},
|
||||
{
|
||||
"icon_id": "16365914",
|
||||
"name": "转码",
|
||||
"font_class": "refresh",
|
||||
"unicode": "e6a2",
|
||||
"unicode_decimal": 59042
|
||||
},
|
||||
{
|
||||
"icon_id": "2674473",
|
||||
"name": "关闭",
|
||||
"font_class": "close2",
|
||||
"unicode": "e624",
|
||||
"unicode_decimal": 58916
|
||||
},
|
||||
{
|
||||
"icon_id": "2674474",
|
||||
"name": "播放",
|
||||
"font_class": "start",
|
||||
"unicode": "e625",
|
||||
"unicode_decimal": 58917
|
||||
},
|
||||
{
|
||||
"icon_id": "2674483",
|
||||
"name": "暂停",
|
||||
"font_class": "pause",
|
||||
"unicode": "e629",
|
||||
"unicode_decimal": 58921
|
||||
},
|
||||
{
|
||||
"icon_id": "1421450",
|
||||
"name": "ok",
|
||||
"font_class": "ok",
|
||||
"unicode": "e613",
|
||||
"unicode_decimal": 58899
|
||||
},
|
||||
{
|
||||
"icon_id": "6151154",
|
||||
"name": "clock-fill",
|
||||
"font_class": "clock",
|
||||
"unicode": "e7d7",
|
||||
"unicode_decimal": 59351
|
||||
},
|
||||
{
|
||||
"icon_id": "11903682",
|
||||
"name": "close",
|
||||
"font_class": "close",
|
||||
"unicode": "e656",
|
||||
"unicode_decimal": 58966
|
||||
},
|
||||
{
|
||||
"icon_id": "300021",
|
||||
"name": "分享给好友icon",
|
||||
"font_class": "share",
|
||||
"unicode": "e65e",
|
||||
"unicode_decimal": 58974
|
||||
},
|
||||
{
|
||||
"icon_id": "760480",
|
||||
"name": "ok",
|
||||
"font_class": "right1",
|
||||
"unicode": "e65b",
|
||||
"unicode_decimal": 58971
|
||||
},
|
||||
{
|
||||
"icon_id": "14410256",
|
||||
"name": "icon_error",
|
||||
"font_class": "error",
|
||||
"unicode": "e651",
|
||||
"unicode_decimal": 58961
|
||||
},
|
||||
{
|
||||
"icon_id": "1185453",
|
||||
"name": "edit",
|
||||
"font_class": "edit",
|
||||
"unicode": "e61f",
|
||||
"unicode_decimal": 58911
|
||||
},
|
||||
{
|
||||
"icon_id": "6353306",
|
||||
"name": "share",
|
||||
"font_class": "share1",
|
||||
"unicode": "e86f",
|
||||
"unicode_decimal": 59503
|
||||
},
|
||||
{
|
||||
"icon_id": "15838518",
|
||||
"name": "move",
|
||||
"font_class": "move",
|
||||
"unicode": "e67b",
|
||||
"unicode_decimal": 59003
|
||||
},
|
||||
{
|
||||
"icon_id": "4766848",
|
||||
"name": "folder-add",
|
||||
"font_class": "folder-add",
|
||||
"unicode": "e7d1",
|
||||
"unicode_decimal": 59345
|
||||
},
|
||||
{
|
||||
"icon_id": "4767011",
|
||||
"name": "right",
|
||||
"font_class": "right",
|
||||
"unicode": "e7eb",
|
||||
"unicode_decimal": 59371
|
||||
},
|
||||
{
|
||||
"icon_id": "6151351",
|
||||
"name": "download",
|
||||
"font_class": "download",
|
||||
"unicode": "e83a",
|
||||
"unicode_decimal": 59450
|
||||
},
|
||||
{
|
||||
"icon_id": "6151353",
|
||||
"name": "upload",
|
||||
"font_class": "upload",
|
||||
"unicode": "e83b",
|
||||
"unicode_decimal": 59451
|
||||
},
|
||||
{
|
||||
"icon_id": "26721527",
|
||||
"name": "AppstoreFilled",
|
||||
"font_class": "all",
|
||||
"unicode": "e6ff",
|
||||
"unicode_decimal": 59135
|
||||
},
|
||||
{
|
||||
"icon_id": "4562501",
|
||||
"name": "自治云",
|
||||
"font_class": "cloude",
|
||||
"unicode": "e66d",
|
||||
"unicode_decimal": 58989
|
||||
},
|
||||
{
|
||||
"icon_id": "6050457",
|
||||
"name": "moviesel",
|
||||
"font_class": "movie2",
|
||||
"unicode": "e61c",
|
||||
"unicode_decimal": 58908
|
||||
},
|
||||
{
|
||||
"icon_id": "741255",
|
||||
"name": "其他",
|
||||
"font_class": "more",
|
||||
"unicode": "e631",
|
||||
"unicode_decimal": 58929
|
||||
},
|
||||
{
|
||||
"icon_id": "145741",
|
||||
"name": "音乐_填充",
|
||||
"font_class": "music",
|
||||
"unicode": "e6a1",
|
||||
"unicode_decimal": 59041
|
||||
},
|
||||
{
|
||||
"icon_id": "212328",
|
||||
"name": "play_fill",
|
||||
"font_class": "video",
|
||||
"unicode": "e74f",
|
||||
"unicode_decimal": 59215
|
||||
},
|
||||
{
|
||||
"icon_id": "14441151",
|
||||
"name": "image",
|
||||
"font_class": "image",
|
||||
"unicode": "e68d",
|
||||
"unicode_decimal": 59021
|
||||
},
|
||||
{
|
||||
"icon_id": "34532161",
|
||||
"name": "网盘-copy",
|
||||
"font_class": "pan",
|
||||
"unicode": "e6e2",
|
||||
"unicode_decimal": 59106
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<span class="avatar" :style="{ width: width + 'px', height: width + 'px' }">
|
||||
<img
|
||||
:src="
|
||||
avatar && avatar != ''
|
||||
? avatar
|
||||
: `${proxy.globalInfo.avatarUrl}${userId}?${timestamp}`
|
||||
"
|
||||
v-if="userId"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance } from "vue";
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const props = defineProps({
|
||||
userId: {
|
||||
type: String,
|
||||
},
|
||||
avatar: {
|
||||
type: String,
|
||||
},
|
||||
timestamp: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 40,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.avatar {
|
||||
display: flex;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
img {
|
||||
width: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="avatar-upload">
|
||||
<div class="avatar-show">
|
||||
<template v-if="localFile">
|
||||
<img :src="localFile" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<img
|
||||
:src="`${modelValue.qqAvatar}`"
|
||||
v-if="modelValue && modelValue.qqAvatar"
|
||||
/>
|
||||
<img :src="`/api/getAvatar/${modelValue.userId}`" v-else />
|
||||
</template>
|
||||
</div>
|
||||
<div class="select-btn">
|
||||
<el-upload
|
||||
name="file"
|
||||
:show-file-list="false"
|
||||
accept=".png,.PNG,.jpg,.JPG,.jpeg,.JPEG,.gif,.GIF,.bmp,.BMP"
|
||||
:multiple="false"
|
||||
:http-request="uploadImage"
|
||||
>
|
||||
<el-button type="primary">选择</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, getCurrentInstance } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const timestamp = ref("");
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const localFile = ref(null);
|
||||
const emit = defineEmits();
|
||||
const uploadImage = async (file) => {
|
||||
file = file.file;
|
||||
let img = new FileReader();
|
||||
img.readAsDataURL(file);
|
||||
img.onload = ({ target }) => {
|
||||
localFile.value = target.result;
|
||||
};
|
||||
emit("update:modelValue", file);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.avatar-upload {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: end;
|
||||
.avatar-show {
|
||||
background: rgb(245, 245, 245);
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
.iconfont {
|
||||
font-size: 50px;
|
||||
color: #ddd;
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.op {
|
||||
position: absolute;
|
||||
color: #0e8aef;
|
||||
top: 80px;
|
||||
}
|
||||
}
|
||||
.select-btn {
|
||||
margin-left: 10px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-dialog
|
||||
:show-close="showClose"
|
||||
:draggable="true"
|
||||
:model-value="show"
|
||||
:close-on-click-modal="false"
|
||||
:title="title"
|
||||
class="cust-dialog"
|
||||
:top="top + 'px'"
|
||||
:width="width"
|
||||
@close="close"
|
||||
>
|
||||
<div
|
||||
class="dialog-body"
|
||||
:style="{ 'max-height': maxHeight + 'px', padding: padding + 'px' }"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<template v-if="(buttons && buttons.length > 0) || showCancel">
|
||||
<div class="dialog-footer">
|
||||
<el-button link @click="close" v-if="showCancel"> 取消 </el-button>
|
||||
<el-button
|
||||
v-for="btn in buttons"
|
||||
:type="btn.type || 'primary'"
|
||||
@click="btn.click"
|
||||
>
|
||||
{{ btn.text }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showClose: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showCancel: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
top: {
|
||||
type: Number,
|
||||
default: 50,
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "30%",
|
||||
},
|
||||
buttons: {
|
||||
type: Array,
|
||||
},
|
||||
padding: {
|
||||
type: Number,
|
||||
default: 15,
|
||||
},
|
||||
});
|
||||
|
||||
const maxHeight = window.innerHeight - props.top - 100;
|
||||
|
||||
const emit = defineEmits();
|
||||
const close = () => {
|
||||
emit("close");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cust-dialog {
|
||||
margin: 30px auto 10px !important;
|
||||
.el-dialog__body {
|
||||
padding: 0px;
|
||||
}
|
||||
.dialog-body {
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 1px solid #ddd;
|
||||
min-height: 80px;
|
||||
overflow: auto;
|
||||
}
|
||||
.dialog-footer {
|
||||
text-align: right;
|
||||
padding: 5px 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<div>
|
||||
<Dialog
|
||||
:show="dialogConfig.show"
|
||||
:title="dialogConfig.title"
|
||||
:buttons="dialogConfig.buttons"
|
||||
width="600px"
|
||||
:showCancel="true"
|
||||
@close="close"
|
||||
>
|
||||
<div class="navigation-panel">
|
||||
<Navigation
|
||||
ref="navigationRef"
|
||||
@navChange="navChange"
|
||||
:watchPath="false"
|
||||
></Navigation>
|
||||
</div>
|
||||
<div class="folder-list" v-if="folderList.length > 0">
|
||||
<div
|
||||
class="folder-item"
|
||||
v-for="item in folderList"
|
||||
@click="selectFolder(item)"
|
||||
>
|
||||
<icon :fileType="0"></icon>
|
||||
<span class="file-name">{{ item.fileName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="tips">
|
||||
移动到 <span>{{ currentFolder.fileName }}</span> 文件夹
|
||||
</div>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, getCurrentInstance, nextTick } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const api = {
|
||||
loadAllFolder: "/file/loadAllFolder",
|
||||
};
|
||||
|
||||
const dialogConfig = ref({
|
||||
show: false,
|
||||
title: "移动到",
|
||||
buttons: [
|
||||
{
|
||||
type: "primary",
|
||||
click: () => {
|
||||
folderSelect();
|
||||
},
|
||||
text: "移动到此",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
//父级ID
|
||||
const filePid = ref("0");
|
||||
const folderList = ref([]);
|
||||
const loadAllFolder = async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.loadAllFolder,
|
||||
params: {
|
||||
filePid: filePid.value,
|
||||
currentFileIds: currentFileIds.value,
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
folderList.value = result.data;
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
dialogConfig.value.show = false;
|
||||
};
|
||||
|
||||
//当前目录,传入后 获取目录需要排除该目录
|
||||
const currentFileIds = ref({});
|
||||
//展示弹出框对外的方法
|
||||
const showFolderDialog = (curFileIds) => {
|
||||
dialogConfig.value.show = true;
|
||||
currentFileIds.value = curFileIds;
|
||||
filePid.value = "0";
|
||||
nextTick(() => {
|
||||
navigationRef.value.init();
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
showFolderDialog,
|
||||
close,
|
||||
});
|
||||
//选择目录
|
||||
const navigationRef = ref();
|
||||
const selectFolder = (data) => {
|
||||
navigationRef.value.openFolder(data);
|
||||
};
|
||||
|
||||
//当前的目录
|
||||
const currentFolder = ref({});
|
||||
//导航改变回调
|
||||
const navChange = (data) => {
|
||||
const { curFolder } = data;
|
||||
currentFolder.value = curFolder;
|
||||
filePid.value = curFolder.fileId;
|
||||
loadAllFolder();
|
||||
};
|
||||
|
||||
const emit = defineEmits(["folderSelect"]);
|
||||
|
||||
const folderSelect = () => {
|
||||
emit("folderSelect", filePid.value);
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.navigation-panel {
|
||||
padding-left: 10px;
|
||||
background: #f1f1f1;
|
||||
}
|
||||
.folder-list {
|
||||
.folder-item {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
.file-name {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
}
|
||||
&:hover {
|
||||
background: #f8f8f8;
|
||||
}
|
||||
}
|
||||
max-height: calc(100vh - 200px);
|
||||
min-height: 200px;
|
||||
}
|
||||
.tips {
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
span {
|
||||
color: #06a7ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<span :style="{ width: width + 'px', height: width + 'px' }" class="icon">
|
||||
<img :src="getImage()" :style="{ 'object-fit': fit }" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, getCurrentInstance } from "vue";
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const props = defineProps({
|
||||
fileType: {
|
||||
type: Number,
|
||||
},
|
||||
iconName: {
|
||||
type: String,
|
||||
},
|
||||
cover: {
|
||||
type: String,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 32,
|
||||
},
|
||||
fit: {
|
||||
type: String,
|
||||
default: "cover",
|
||||
},
|
||||
});
|
||||
|
||||
const fileTypeMap = {
|
||||
0: { desc: "目录", icon: "folder" },
|
||||
1: { desc: "视频", icon: "video" },
|
||||
2: { desc: "音频", icon: "music" },
|
||||
3: { desc: "图片", icon: "image" },
|
||||
4: { desc: "exe", icon: "pdf" },
|
||||
5: { desc: "doc", icon: "word" },
|
||||
6: { desc: "excel", icon: "excel" },
|
||||
7: { desc: "纯文本", icon: "txt" },
|
||||
8: { desc: "程序", icon: "code" },
|
||||
9: { desc: "压缩包", icon: "zip" },
|
||||
10: { desc: "其他文件", icon: "others" },
|
||||
};
|
||||
|
||||
const getImage = () => {
|
||||
if (props.cover) {
|
||||
return proxy.globalInfo.imageUrl + props.cover;
|
||||
}
|
||||
let icon = "unknow_icon";
|
||||
if (props.iconName) {
|
||||
icon = props.iconName;
|
||||
} else {
|
||||
console.log(props.fileType);
|
||||
const iconMap = fileTypeMap[props.fileType];
|
||||
if (iconMap != undefined) {
|
||||
icon = iconMap["icon"];
|
||||
}
|
||||
}
|
||||
return new URL(`/src/assets/icon-image/${icon}.png`, import.meta.url).href;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon {
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,213 @@
|
||||
<template>
|
||||
<div class="top-navigation">
|
||||
<template v-if="folderList.length > 0">
|
||||
<span class="back link" @click="backParent">返回上一级</span>
|
||||
<el-divider direction="vertical" />
|
||||
</template>
|
||||
<span v-if="folderList.length == 0" class="all-file">全部文件</span>
|
||||
<span
|
||||
class="link"
|
||||
@click="setCurrentFolder(-1)"
|
||||
v-if="folderList.length > 0"
|
||||
>全部文件</span
|
||||
>
|
||||
<template v-for="(item, index) in folderList">
|
||||
<span class="iconfont icon-right"></span>
|
||||
<span
|
||||
class="link"
|
||||
@click="setCurrentFolder(index)"
|
||||
v-if="index < folderList.length - 1"
|
||||
>{{ item.fileName }}</span
|
||||
>
|
||||
<span v-if="index == folderList.length - 1" class="text">{{
|
||||
item.fileName
|
||||
}}</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, getCurrentInstance, watch } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const props = defineProps({
|
||||
watchPath: {
|
||||
type: Boolean, //是否监听路径变化
|
||||
default: true,
|
||||
},
|
||||
shareId: {
|
||||
type: String,
|
||||
},
|
||||
adminShow: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
//初始化
|
||||
const init = () => {
|
||||
folderList.value = [];
|
||||
currentFolder.value = { fileId: "0" };
|
||||
doCallback();
|
||||
};
|
||||
|
||||
//点击目录
|
||||
const openFolder = (data) => {
|
||||
const { fileId, fileName } = data;
|
||||
const folder = {
|
||||
fileName: fileName,
|
||||
fileId: fileId,
|
||||
};
|
||||
folderList.value.push(folder);
|
||||
currentFolder.value = folder;
|
||||
setPath();
|
||||
};
|
||||
|
||||
defineExpose({ openFolder, init });
|
||||
|
||||
const api = {
|
||||
getFolderInfo: "/file/getFolderInfo",
|
||||
getFolderInfo4Share: "/showShare/getFolderInfo",
|
||||
getFolderInfo4Admin: "/admin/getFolderInfo",
|
||||
};
|
||||
|
||||
//分类
|
||||
const category = ref();
|
||||
//目录
|
||||
const folderList = ref([]);
|
||||
//当前目录
|
||||
const currentFolder = ref({ fileId: "0" });
|
||||
|
||||
//返回上一级
|
||||
const backParent = () => {
|
||||
let currentIndex = null;
|
||||
for (let i = 0; i < folderList.value.length; i++) {
|
||||
if (folderList.value[i].fileId == currentFolder.value.fileId) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
setCurrentFolder(currentIndex - 1);
|
||||
};
|
||||
|
||||
//点击导航 设置当前目录
|
||||
const setCurrentFolder = (index) => {
|
||||
if (index == -1) {
|
||||
//返回全部
|
||||
currentFolder.value = { fileId: "0" };
|
||||
folderList.value = [];
|
||||
} else {
|
||||
currentFolder.value = folderList.value[index];
|
||||
folderList.value.splice(index + 1, folderList.value.length);
|
||||
}
|
||||
setPath();
|
||||
};
|
||||
|
||||
//设置URL路径
|
||||
const setPath = () => {
|
||||
if (!props.watchPath) {
|
||||
doCallback();
|
||||
return;
|
||||
}
|
||||
let pathArray = [];
|
||||
folderList.value.forEach((item) => {
|
||||
pathArray.push(item.fileId);
|
||||
});
|
||||
router.push({
|
||||
path: route.path,
|
||||
query:
|
||||
pathArray.length == 0
|
||||
? ""
|
||||
: {
|
||||
path: pathArray.join("/"),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
//获取当前路径的目录
|
||||
const getNavigationFolder = async (path) => {
|
||||
let url = api.getFolderInfo;
|
||||
if (props.shareId) {
|
||||
url = api.getFolderInfo4Share;
|
||||
}
|
||||
if (props.adminShow) {
|
||||
url = api.getFolderInfo4Admin;
|
||||
}
|
||||
|
||||
let result = await proxy.Request({
|
||||
url: url,
|
||||
showLoading: false,
|
||||
params: {
|
||||
path: path,
|
||||
shareId: props.shareId,
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
folderList.value = result.data;
|
||||
};
|
||||
|
||||
const emit = defineEmits(["navChange"]);
|
||||
const doCallback = () => {
|
||||
emit("navChange", {
|
||||
categoryId: category.value,
|
||||
curFolder: currentFolder.value,
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route,
|
||||
(newVal, oldVal) => {
|
||||
if (!props.watchPath) {
|
||||
return;
|
||||
}
|
||||
//路由切换到其他路由 首页和管理员查看文件列表页面需要监听
|
||||
if (
|
||||
newVal.path.indexOf("/main") === -1 &&
|
||||
newVal.path.indexOf("/settings/fileList") === -1 &&
|
||||
newVal.path.indexOf("/share") === -1
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const path = newVal.query.path;
|
||||
const categoryId = newVal.params.category;
|
||||
category.value = categoryId;
|
||||
if (path == undefined) {
|
||||
init();
|
||||
} else {
|
||||
getNavigationFolder(path);
|
||||
//设置当前目录
|
||||
let pathArray = path.split("/");
|
||||
currentFolder.value = {
|
||||
fileId: pathArray[pathArray.length - 1],
|
||||
};
|
||||
doCallback();
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.top-navigation {
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 40px;
|
||||
.all-file {
|
||||
font-weight: bold;
|
||||
}
|
||||
.link {
|
||||
color: #06a7ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.icon-right {
|
||||
color: #06a7ff;
|
||||
padding: 0px 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="no-data">
|
||||
<div class="iconfont icon-empty"></div>
|
||||
<div class="msg">{{ msg }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
msg: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 10px 0px;
|
||||
.icon-empty {
|
||||
font-size: 50px;
|
||||
color: #bbb;
|
||||
}
|
||||
.msg {
|
||||
margin-top: 10px;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table
|
||||
ref="dataTable"
|
||||
:data="dataSource.list || []"
|
||||
:height="tableHeight"
|
||||
:stripe="options.stripe"
|
||||
:border="options.border"
|
||||
header-row-class-name="table-header-row"
|
||||
highlight-current-row
|
||||
@row-click="handleRowClick"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<!--selection选择框-->
|
||||
<el-table-column
|
||||
v-if="options.selectType && options.selectType == 'checkbox'"
|
||||
type="selection"
|
||||
width="50"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<!--序号-->
|
||||
<el-table-column
|
||||
v-if="options.showIndex"
|
||||
label="序号"
|
||||
type="index"
|
||||
width="60"
|
||||
align="center"
|
||||
></el-table-column>
|
||||
<!--数据列-->
|
||||
<template v-for="(column, index) in columns">
|
||||
<template v-if="column.scopedSlots">
|
||||
<el-table-column
|
||||
:key="index"
|
||||
:prop="column.prop"
|
||||
:label="column.label"
|
||||
:align="column.align || 'left'"
|
||||
:width="column.width"
|
||||
>
|
||||
<template #default="scope">
|
||||
<slot
|
||||
:name="column.scopedSlots"
|
||||
:index="scope.$index"
|
||||
:row="scope.row"
|
||||
>
|
||||
</slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-table-column
|
||||
:key="index"
|
||||
:prop="column.prop"
|
||||
:label="column.label"
|
||||
:align="column.align || 'left'"
|
||||
:width="column.width"
|
||||
:fixed="column.fixed"
|
||||
>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</template>
|
||||
</el-table>
|
||||
<!-- 分页 -->
|
||||
<div class="pagination" v-if="showPagination">
|
||||
<el-pagination
|
||||
v-if="dataSource.totalCount"
|
||||
background
|
||||
:total="dataSource.totalCount"
|
||||
:page-sizes="[15, 30, 50, 100]"
|
||||
:page-size="dataSource.pageSize"
|
||||
:current-page.sync="dataSource.pageNo"
|
||||
:layout="layout"
|
||||
@size-change="handlePageSizeChange"
|
||||
@current-change="handlePageNoChange"
|
||||
style="text-align: right"
|
||||
></el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
const emit = defineEmits(["rowSelected", "rowClick"]);
|
||||
const props = defineProps({
|
||||
dataSource: Object,
|
||||
showPagination: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showPageSize: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
options: {
|
||||
type: Object,
|
||||
default: {
|
||||
extHeight: 0,
|
||||
showIndex: false,
|
||||
},
|
||||
},
|
||||
columns: Array,
|
||||
fetch: Function, // 获取数据的函数
|
||||
initFetch: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
const layout = computed(() => {
|
||||
return `total, ${
|
||||
props.showPageSize ? "sizes" : ""
|
||||
}, prev, pager, next, jumper`;
|
||||
});
|
||||
//顶部 60 , 内容区域距离顶部 20, 内容上下内间距 15*2 分页区域高度 46
|
||||
const topHeight = 60 + 20 + 30 + 46;
|
||||
|
||||
const tableHeight = ref(
|
||||
props.options.tableHeight
|
||||
? props.options.tableHeight
|
||||
: window.innerHeight - topHeight - props.options.extHeight
|
||||
);
|
||||
|
||||
//初始化
|
||||
const init = () => {
|
||||
if (props.initFetch && props.fetch) {
|
||||
props.fetch();
|
||||
}
|
||||
};
|
||||
init();
|
||||
|
||||
const dataTable = ref();
|
||||
//清除选中
|
||||
const clearSelection = () => {
|
||||
dataTable.value.clearSelection();
|
||||
};
|
||||
|
||||
//设置行选中
|
||||
const setCurrentRow = (rowKey, rowValue) => {
|
||||
let row = props.dataSource.list.find((item) => {
|
||||
return item[rowKey] === rowValue;
|
||||
});
|
||||
dataTable.value.setCurrentRow(row);
|
||||
};
|
||||
//将子组件暴露出去,否则父组件无法调用
|
||||
defineExpose({ setCurrentRow, clearSelection });
|
||||
|
||||
//行点击
|
||||
const handleRowClick = (row) => {
|
||||
emit("rowClick", row);
|
||||
};
|
||||
|
||||
//多选
|
||||
const handleSelectionChange = (row) => {
|
||||
emit("rowSelected", row);
|
||||
};
|
||||
|
||||
//切换每页大小
|
||||
const handlePageSizeChange = (size) => {
|
||||
props.dataSource.pageSize = size;
|
||||
props.dataSource.pageNo = 1;
|
||||
props.fetch();
|
||||
};
|
||||
// 切换页码
|
||||
const handlePageNoChange = (pageNo) => {
|
||||
props.dataSource.pageNo = pageNo;
|
||||
props.fetch();
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.pagination {
|
||||
padding-top: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.el-pagination {
|
||||
justify-content: right;
|
||||
}
|
||||
|
||||
:deep .el-table__cell {
|
||||
padding: 4px 0px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div class="window" v-if="show">
|
||||
<div class="window-mask" v-if="show" @click="close"></div>
|
||||
<div class="close" @click="close">
|
||||
<span class="iconfont icon-close2"> </span>
|
||||
</div>
|
||||
<div
|
||||
class="window-content"
|
||||
:style="{
|
||||
top: '0px',
|
||||
left: windowContentLeft + 'px',
|
||||
width: windowContentWidth + 'px',
|
||||
}"
|
||||
>
|
||||
<div class="title">
|
||||
{{ title }}
|
||||
</div>
|
||||
<div class="content-body" :style="{ 'align-items': align }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
type: Boolean,
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 1000,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
default: "top",
|
||||
},
|
||||
});
|
||||
|
||||
const windowWidth = ref(window.innerWidth);
|
||||
const windowContentWidth = computed(() => {
|
||||
return props.width > windowWidth.value ? windowWidth.value : props.width;
|
||||
});
|
||||
|
||||
const windowContentLeft = computed(() => {
|
||||
let left = windowWidth.value - props.width;
|
||||
return left < 0 ? 0 : left / 2;
|
||||
});
|
||||
|
||||
const emit = defineEmits(["close"]);
|
||||
const close = () => {
|
||||
emit("close");
|
||||
};
|
||||
|
||||
const resizeWindow = () => {
|
||||
windowWidth.value = window.innerWidth;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener("resize", resizeWindow);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("resize", resizeWindow);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.window {
|
||||
.window-mask {
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: calc(100vh);
|
||||
z-index: 200;
|
||||
opacity: 0.5;
|
||||
background: #000;
|
||||
position: fixed;
|
||||
}
|
||||
.close {
|
||||
z-index: 202;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
right: 30px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 22px;
|
||||
background: #606266;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.iconfont {
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
z-index: 100000;
|
||||
}
|
||||
}
|
||||
.window-content {
|
||||
top: 0px;
|
||||
z-index: 201;
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
.title {
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
font-weight: bold;
|
||||
}
|
||||
.content-body {
|
||||
height: calc(100vh - 41px);
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<PreviewImage
|
||||
ref="imageViewerRef"
|
||||
:imageList="[imageUrl]"
|
||||
v-if="fileInfo.fileCategory == 3"
|
||||
></PreviewImage>
|
||||
<Window
|
||||
:show="windowShow"
|
||||
@close="closeWindow"
|
||||
:width="fileInfo.fileCategory == 1 ? 1500 : 900"
|
||||
:title="fileInfo.fileName"
|
||||
:align="fileInfo.fileCategory == 1 ? 'center' : 'top'"
|
||||
v-else
|
||||
>
|
||||
<PreviewVideo :url="url" v-if="fileInfo.fileCategory == 1"></PreviewVideo>
|
||||
<PreviewExcel :url="url" v-if="fileInfo.fileType == 6"></PreviewExcel>
|
||||
<PreviewDoc :url="url" v-if="fileInfo.fileType == 5"></PreviewDoc>
|
||||
<PreviewPdf :url="url" v-if="fileInfo.fileType == 4"></PreviewPdf>
|
||||
<PreviewTxt
|
||||
:url="url"
|
||||
v-if="fileInfo.fileType == 7 || fileInfo.fileType == 8"
|
||||
></PreviewTxt>
|
||||
<!--特殊预览-->
|
||||
<PreviewMusic
|
||||
:url="url"
|
||||
:fileName="fileInfo.fileName"
|
||||
v-if="fileInfo.fileCategory == 2"
|
||||
></PreviewMusic>
|
||||
<PreviewDownload
|
||||
:createDownloadUrl="createDownloadUrl"
|
||||
:downloadUrl="downloadUrl"
|
||||
:fileInfo="fileInfo"
|
||||
v-if="fileInfo.fileCategory == 5 && fileInfo.fileType != 8"
|
||||
></PreviewDownload>
|
||||
</Window>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import PreviewDoc from "@/components/preview/PreviewDoc.vue";
|
||||
import PreviewExcel from "@/components/preview/PreviewExcel.vue";
|
||||
import PreviewImage from "@/components/preview/PreviewImage.vue";
|
||||
import PreviewPdf from "@/components/preview/PreviewPdf.vue";
|
||||
import PreviewVideo from "@/components/preview/PreviewVideo.vue";
|
||||
import PreviewTxt from "@/components/preview/PreviewTxt.vue";
|
||||
import PreviewDownload from "@/components/preview/PreviewDownload.vue";
|
||||
import PreviewMusic from "@/components/preview/PreviewMusic.vue";
|
||||
|
||||
import { ref, reactive, getCurrentInstance, nextTick, computed } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const imageUrl = computed(() => {
|
||||
return (
|
||||
proxy.globalInfo.imageUrl + fileInfo.value.fileCover.replaceAll("_.", ".")
|
||||
);
|
||||
});
|
||||
|
||||
const windowShow = ref(false);
|
||||
const closeWindow = () => {
|
||||
windowShow.value = false;
|
||||
};
|
||||
const FILE_URL_MAP = {
|
||||
0: {
|
||||
fileUrl: "/file/getFile",
|
||||
videoUrl: "/file/ts/getVideoInfo",
|
||||
createDownloadUrl: "/file/createDownloadUrl",
|
||||
downloadUrl: "/api/file/download",
|
||||
},
|
||||
1: {
|
||||
fileUrl: "/admin/getFile",
|
||||
videoUrl: "/admin/ts/getVideoInfo",
|
||||
createDownloadUrl: "/admin/createDownloadUrl",
|
||||
downloadUrl: "/api/admin/download",
|
||||
},
|
||||
2: {
|
||||
fileUrl: "/showShare/getFile",
|
||||
videoUrl: "/showShare/ts/getVideoInfo",
|
||||
createDownloadUrl: "/showShare/createDownloadUrl",
|
||||
downloadUrl: "/api/showShare/download",
|
||||
},
|
||||
};
|
||||
const url = ref(null);
|
||||
const createDownloadUrl = ref(null);
|
||||
const downloadUrl = ref(null);
|
||||
|
||||
const fileInfo = ref({});
|
||||
|
||||
const imageViewerRef = ref();
|
||||
const showPreview = (data, showPart) => {
|
||||
fileInfo.value = data;
|
||||
if (data.fileCategory == 3) {
|
||||
nextTick(() => {
|
||||
imageViewerRef.value.show(0);
|
||||
});
|
||||
} else {
|
||||
windowShow.value = true;
|
||||
let _url = FILE_URL_MAP[showPart].fileUrl;
|
||||
//视频地址单独处理
|
||||
if (data.fileCategory == 1) {
|
||||
_url = FILE_URL_MAP[showPart].videoUrl;
|
||||
}
|
||||
let _createDownloadUrl = FILE_URL_MAP[showPart].createDownloadUrl;
|
||||
let _downloadUrl = FILE_URL_MAP[showPart].downloadUrl;
|
||||
if (showPart == 0) {
|
||||
_url = _url + "/" + data.fileId;
|
||||
_createDownloadUrl = _createDownloadUrl + "/" + data.fileId;
|
||||
} else if (showPart == 1) {
|
||||
_url = _url + "/" + data.userId + "/" + data.fileId;
|
||||
_createDownloadUrl =
|
||||
_createDownloadUrl + "/" + data.userId + "/" + data.fileId;
|
||||
} else if (showPart == 2) {
|
||||
_url = _url + "/" + data.shareId + "/" + data.fileId;
|
||||
_createDownloadUrl =
|
||||
_createDownloadUrl + "/" + data.shareId + "/" + data.fileId;
|
||||
}
|
||||
url.value = _url;
|
||||
createDownloadUrl.value = _createDownloadUrl;
|
||||
downloadUrl.value = _downloadUrl;
|
||||
}
|
||||
};
|
||||
defineExpose({ showPreview });
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div ref="docRef" class="doc-content"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import * as docx from "docx-preview";
|
||||
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
const docRef = ref();
|
||||
const initDoc = async () => {
|
||||
let result = await proxy.Request({
|
||||
url: props.url,
|
||||
responseType: "blob",
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
docx.renderAsync(result, docRef.value);
|
||||
};
|
||||
onMounted(() => {
|
||||
initDoc();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.doc-content {
|
||||
margin: 0px auto;
|
||||
:deep .docx-wrapper {
|
||||
background: #fff;
|
||||
padding: 10px 0px;
|
||||
}
|
||||
|
||||
:deep .docx-wrapper > section.docx {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div class="others">
|
||||
<div class="body-content">
|
||||
<div>
|
||||
<icon
|
||||
:iconName="fileInfo.fileType == 9 ? 'zip' : 'others'"
|
||||
:width="80"
|
||||
></icon>
|
||||
</div>
|
||||
<div class="file-name">{{ fileInfo.fileName }}</div>
|
||||
<div class="tips">该类型的文件暂不支持预览,请下载后查看</div>
|
||||
<div class="download-btn">
|
||||
<el-button type="primary" @click="download"
|
||||
>点击下载 {{ proxy.Utils.sizeToStr(fileInfo.fileSize) }}</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, getCurrentInstance } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const props = defineProps({
|
||||
createDownloadUrl: {
|
||||
type: String,
|
||||
},
|
||||
downloadUrl: {
|
||||
type: String,
|
||||
},
|
||||
fileInfo: {
|
||||
type: Object,
|
||||
},
|
||||
});
|
||||
|
||||
//下载文件
|
||||
const download = async () => {
|
||||
let result = await proxy.Request({
|
||||
url: props.createDownloadUrl,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
window.location.href = props.downloadUrl + "/" + result.data;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.others {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
.body-content {
|
||||
text-align: center;
|
||||
.file-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
.tips {
|
||||
color: #999898;
|
||||
margin-top: 5px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.download-btn {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div v-html="excelContent" class="talbe-info"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import * as XLSX from "xlsx";
|
||||
import { ref, reactive, getCurrentInstance, onMounted } from "vue";
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
const excelContent = ref();
|
||||
const initExcel = async () => {
|
||||
let result = await proxy.Request({
|
||||
url: props.url,
|
||||
responseType: "arraybuffer",
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
let workbook = XLSX.read(new Uint8Array(result), { type: "array" }); // 解析数据
|
||||
var worksheet = workbook.Sheets[workbook.SheetNames[0]]; // workbook.SheetNames 下存的是该文件每个工作表名字,这里取出第一个工作表
|
||||
excelContent.value = XLSX.utils.sheet_to_html(worksheet);
|
||||
};
|
||||
onMounted(() => {
|
||||
initExcel();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.talbe-info {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
:deep table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
td {
|
||||
border: 1px solid #ddd;
|
||||
border-collapse: collapse;
|
||||
padding: 5px;
|
||||
height: 30px;
|
||||
min-width: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="image-viewer">
|
||||
<el-image-viewer
|
||||
:initial-index="previewImgIndex"
|
||||
hide-on-click-modal
|
||||
:url-list="imageList"
|
||||
@close="closeImgViewer"
|
||||
v-if="previewImgIndex != null"
|
||||
>
|
||||
</el-image-viewer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
const prosp = defineProps({
|
||||
imageList: {
|
||||
type: Array,
|
||||
},
|
||||
});
|
||||
|
||||
const previewImgIndex = ref(null);
|
||||
|
||||
const show = (index) => {
|
||||
stopScroll();
|
||||
previewImgIndex.value = index;
|
||||
};
|
||||
|
||||
defineExpose({ show });
|
||||
|
||||
const closeImgViewer = () => {
|
||||
startScroll();
|
||||
previewImgIndex.value = null;
|
||||
};
|
||||
|
||||
//禁止滚动
|
||||
const stopScroll = () => {
|
||||
document.body.style.overflow = "hidden";
|
||||
};
|
||||
|
||||
// 开始滚动
|
||||
const startScroll = () => {
|
||||
document.body.style.overflow = "auto";
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.image-viewer {
|
||||
.el-image-viewer__mask {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="music">
|
||||
<div class="body-content">
|
||||
<div class="cover">
|
||||
<img src="@/assets/music_cover.png" />
|
||||
</div>
|
||||
<div ref="playerRef" class="music-player"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import APlayer from "APlayer";
|
||||
import "APlayer/dist/APlayer.min.css";
|
||||
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
getCurrentInstance,
|
||||
computed,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
} from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
},
|
||||
fileName: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
const playerRef = ref();
|
||||
const player = ref();
|
||||
onMounted(() => {
|
||||
player.value = new APlayer({
|
||||
container: playerRef.value,
|
||||
audio: {
|
||||
url: `/api${props.url}`,
|
||||
name: `${props.fileName}`,
|
||||
cover: new URL(`@/assets/music_icon.png`, import.meta.url).href,
|
||||
artist: "",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
player.value.destroy();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.music {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
.body-content {
|
||||
text-align: center;
|
||||
width: 80%;
|
||||
.cover {
|
||||
margin: 0px auto;
|
||||
width: 200px;
|
||||
text-align: center;
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.music-player {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="pdf">
|
||||
<vue-pdf-embed
|
||||
ref="pdfRef"
|
||||
:source="state.url"
|
||||
class="vue-pdf-embed"
|
||||
width="850"
|
||||
:page="state.pageNum"
|
||||
:style="scaleFun"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import VuePdfEmbed from "vue-pdf-embed";
|
||||
import { createLoadingTask } from "vue3-pdfjs";
|
||||
import { ElLoading } from "element-plus";
|
||||
|
||||
import { ref, reactive, getCurrentInstance, computed } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
const scaleFun = computed(() => {
|
||||
return "transform:scale(${state.scale})";
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
url: "", // 预览pdf文件地址
|
||||
pageNum: 0, // 当前页面
|
||||
numPages: 0, // 总页数
|
||||
});
|
||||
|
||||
const init = () => {
|
||||
const url = "/api" + props.url;
|
||||
state.url = url;
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: "加载中......",
|
||||
background: "rgba(0, 0, 0, 0.7)",
|
||||
});
|
||||
const loadingTask = createLoadingTask(state.url);
|
||||
loadingTask.promise.then((pdf) => {
|
||||
loading.close();
|
||||
state.numPages = pdf.numPages;
|
||||
});
|
||||
};
|
||||
|
||||
init();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pdf {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<div class="code">
|
||||
<div class="top-op">
|
||||
<div class="encode-select">
|
||||
<el-select
|
||||
placeholder="选择编码"
|
||||
v-model="encode"
|
||||
@change="changeEncode"
|
||||
>
|
||||
<el-option value="utf8" label="utf8编码"></el-option>
|
||||
<el-option value="gbk" label="gbk编码"></el-option>
|
||||
</el-select>
|
||||
<div class="tips">乱码了?切换编码试试</div>
|
||||
</div>
|
||||
<div class="copy-btn">
|
||||
<el-button type="primary" @click="copy">复制</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<highlightjs autodetect :code="txtContent" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import useClipboard from "vue-clipboard3";
|
||||
const { toClipboard } = useClipboard();
|
||||
|
||||
import { ref, reactive, getCurrentInstance, onMounted, nextTick } from "vue";
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
const codeRef = ref();
|
||||
const txtContent = ref("");
|
||||
const blobResult = ref();
|
||||
const encode = ref("utf8");
|
||||
const readTxt = async () => {
|
||||
let result = await proxy.Request({
|
||||
url: props.url,
|
||||
responseType: "blob",
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
blobResult.value = result;
|
||||
showTxt();
|
||||
};
|
||||
|
||||
const changeEncode = (e) => {
|
||||
encode.value = e;
|
||||
showTxt();
|
||||
};
|
||||
|
||||
const showTxt = () => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
let txt = reader.result;
|
||||
txtContent.value = txt; //获取的数据data
|
||||
};
|
||||
reader.readAsText(blobResult.value, encode.value);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
readTxt();
|
||||
});
|
||||
|
||||
const copy = async () => {
|
||||
await toClipboard(txtContent.value);
|
||||
proxy.Message.success("复制成功");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.code {
|
||||
width: 100%;
|
||||
.top-op {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.encode-select {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 5px 10px;
|
||||
.tips {
|
||||
margin-left: 10px;
|
||||
color: #828282;
|
||||
}
|
||||
}
|
||||
.copy-btn {
|
||||
margin-right: 10px;
|
||||
}
|
||||
pre {
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<div ref="player" id="player"></div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import DPlayer from "dplayer";
|
||||
import { nextTick, onMounted, ref, getCurrentInstance } from "vue";
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
},
|
||||
});
|
||||
|
||||
const videoInfo = ref({
|
||||
video: null,
|
||||
});
|
||||
|
||||
const player = ref();
|
||||
const initPlayer = () => {
|
||||
const dp = new DPlayer({
|
||||
element: player.value,
|
||||
theme: "#b7daff",
|
||||
screenshot: true,
|
||||
video: {
|
||||
// pic: videoInfo.img, // 封面
|
||||
url: `/api${props.url}`,
|
||||
type: "customHls",
|
||||
customType: {
|
||||
customHls: function (video, player) {
|
||||
const hls = new Hls();
|
||||
hls.loadSource(video.src);
|
||||
hls.attachMedia(video);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initPlayer();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#player {
|
||||
width: 100%;
|
||||
:deep .dplayer-video-wrap {
|
||||
text-align: center;
|
||||
.dplayer-video {
|
||||
margin: 0px auto;
|
||||
max-height: calc(100vh - 41px);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,20 @@
|
||||
export default {
|
||||
"all": {
|
||||
accept: "*"
|
||||
},
|
||||
"video": {
|
||||
accept: ".mp4,.avi,.rmvb,.mkv,.mov"
|
||||
},
|
||||
"music": {
|
||||
accept: ".mp3,.wav,.wma,.mp2,.flac,.midi,.ra,.ape,.aac,.cda"
|
||||
},
|
||||
"image": {
|
||||
accept: ".jpeg,.jpg,.png,.gif,.bmp,.dds,.psd,.pdt,.webp,.xmp,.svg,.tiff"
|
||||
},
|
||||
"doc": {
|
||||
accept: ".pdf,.doc,.docx,.xls,.xlsx,.txt"
|
||||
},
|
||||
"others": {
|
||||
accept: "*"
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from '@/App.vue'
|
||||
import router from '@/router'
|
||||
//引入cookies
|
||||
import VueCookies from 'vue-cookies'
|
||||
//引入element plus
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
//图标 图标在附件中
|
||||
import '@/assets/icon/iconfont.css'
|
||||
import '@/assets/base.scss'
|
||||
|
||||
//引入代码高亮
|
||||
import HljsVuePlugin from '@highlightjs/vue-plugin'
|
||||
import "highlight.js/styles/atom-one-light.css";
|
||||
import 'highlight.js/lib/common'
|
||||
|
||||
|
||||
import Request from '@/utils/Request';
|
||||
import Message from '@/utils/Message'
|
||||
import Confirm from '@/utils/Confirm'
|
||||
import Verify from '@/utils/Verify'
|
||||
import Utils from '@/utils/Utils'
|
||||
|
||||
//自定义组件
|
||||
import Icon from "@/components/Icon.vue"
|
||||
import Table from '@/components/Table.vue'
|
||||
import Dialog from '@/components/Dialog.vue'
|
||||
import NoData from '@/components/NoData.vue'
|
||||
import Window from '@/components/Window.vue'
|
||||
import Preview from '@/components/preview/Preview.vue'
|
||||
import Navigation from '@/components/Navigation.vue'
|
||||
import FolderSelect from '@/components/FolderSelect.vue'
|
||||
import Avatar from '@/components/Avatar.vue'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(ElementPlus);
|
||||
app.use(createPinia())
|
||||
app.use(HljsVuePlugin);
|
||||
app.use(router)
|
||||
|
||||
app.component("Icon", Icon);
|
||||
app.component("Table", Table);
|
||||
app.component("Dialog", Dialog);
|
||||
app.component("NoData", NoData);
|
||||
app.component("Window", Window);
|
||||
app.component("Preview", Preview);
|
||||
app.component("Navigation", Navigation);
|
||||
app.component("FolderSelect", FolderSelect);
|
||||
app.component("Avatar", Avatar);
|
||||
|
||||
|
||||
//配置全局变量
|
||||
app.config.globalProperties.Request = Request;
|
||||
app.config.globalProperties.Message = Message;
|
||||
app.config.globalProperties.Confirm = Confirm;
|
||||
app.config.globalProperties.Verify = Verify;
|
||||
app.config.globalProperties.Utils = Utils;
|
||||
|
||||
app.config.globalProperties.VueCookies = VueCookies;
|
||||
app.config.globalProperties.globalInfo = {
|
||||
avatarUrl: "/api/getAvatar/",
|
||||
imageUrl: "/api/file/getImage/"
|
||||
}
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,101 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import VueCookies from 'vue-cookies'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/login',
|
||||
name: '登录',
|
||||
component: () => import("@/views/Login.vue")
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
component: () => import("@/views/Framework.vue"),
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: "/main/all"
|
||||
},
|
||||
{
|
||||
path: '/main/:category',
|
||||
name: '首页',
|
||||
meta: {
|
||||
needLogin: true,
|
||||
menuCode: "main"
|
||||
},
|
||||
component: () => import("@/views/main/Main.vue")
|
||||
},
|
||||
{
|
||||
path: '/myshare',
|
||||
name: '我的分享',
|
||||
meta: {
|
||||
needLogin: true,
|
||||
menuCode: "share"
|
||||
},
|
||||
component: () => import("@/views/share/Share.vue")
|
||||
},
|
||||
{
|
||||
path: '/recycle',
|
||||
name: '回收站',
|
||||
meta: {
|
||||
needLogin: true,
|
||||
menuCode: "recycle"
|
||||
},
|
||||
component: () => import("@/views/recycle/Recycle.vue")
|
||||
},
|
||||
{
|
||||
path: '/settings/sysSetting',
|
||||
name: '系统设置',
|
||||
meta: {
|
||||
needLogin: true,
|
||||
menuCode: "settings"
|
||||
},
|
||||
component: () => import("@/views/admin/SysSettings.vue")
|
||||
},
|
||||
{
|
||||
path: '/settings/userList',
|
||||
name: '用户管理',
|
||||
meta: {
|
||||
needLogin: true,
|
||||
menuCode: "settings"
|
||||
},
|
||||
component: () => import("@/views/admin/UserList.vue")
|
||||
},
|
||||
{
|
||||
path: '/settings/fileList',
|
||||
name: '用户文件',
|
||||
meta: {
|
||||
needLogin: true,
|
||||
menuCode: "settings"
|
||||
},
|
||||
component: () => import("@/views/admin/FileList.vue")
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/shareCheck/:shareId',
|
||||
name: '分享校验',
|
||||
component: () => import("@/views/webshare/ShareCheck.vue")
|
||||
},
|
||||
{
|
||||
path: '/share/:shareId',
|
||||
name: '分享',
|
||||
component: () => import("@/views/webshare/Share.vue")
|
||||
}, {
|
||||
path: '/qqlogincalback',
|
||||
name: "qq登录回调",
|
||||
component: () => import('@/views/QqLoginCallback.vue'),
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
const userInfo = VueCookies.get("userInfo");
|
||||
if (to.meta.needLogin != null && to.meta.needLogin && userInfo == null) {
|
||||
router.push("/login");
|
||||
}
|
||||
next();
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,19 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const userStore = defineStore('userInfo', {
|
||||
state: () => {
|
||||
return {
|
||||
userInfo: {},
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getUserInfo() {
|
||||
return this.userInfo;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
saveUserInfo(userInfo) {
|
||||
this.userInfo = userInfo
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
|
||||
const confirm = (message, okfun) => {
|
||||
ElMessageBox.confirm(message, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info',
|
||||
}).then(() => {
|
||||
okfun();
|
||||
}).catch(() => { })
|
||||
};
|
||||
|
||||
export default confirm;
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const showMessage = (msg, callback, type) => {
|
||||
ElMessage({
|
||||
type: type,
|
||||
message: msg,
|
||||
duration: 2000,
|
||||
onClose: () => {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const message = {
|
||||
error: (msg, callback) => {
|
||||
showMessage(msg, callback, "error");
|
||||
},
|
||||
success: (msg, callback) => {
|
||||
showMessage(msg, callback, "success");
|
||||
},
|
||||
warning: (msg, callback) => {
|
||||
showMessage(msg, callback, "warning");
|
||||
},
|
||||
}
|
||||
|
||||
export default message;
|
||||
@@ -0,0 +1,111 @@
|
||||
import axios from 'axios'
|
||||
|
||||
import { ElLoading } from 'element-plus'
|
||||
import router from '@/router'
|
||||
|
||||
import Message from '../utils/Message'
|
||||
|
||||
const contentTypeForm = 'application/x-www-form-urlencoded;charset=UTF-8'
|
||||
const contentTypeJson = 'application/json'
|
||||
//arraybuffer ArrayBuffer对象
|
||||
//blob Blob对象
|
||||
//document Documnet对象
|
||||
//json JavaScript object, parsed from a JSON string returned by the server
|
||||
//text DOMString
|
||||
const responseTypeJson = "json"
|
||||
|
||||
let loading = null;
|
||||
const instance = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 10 * 1000,
|
||||
});
|
||||
//请求前拦截器
|
||||
instance.interceptors.request.use(
|
||||
(config) => {
|
||||
if (config.showLoading) {
|
||||
loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '加载中......',
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
});
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
if (config.showLoading && loading) {
|
||||
loading.close();
|
||||
}
|
||||
Message.error("请求发送失败");
|
||||
return Promise.reject("请求发送失败");
|
||||
}
|
||||
);
|
||||
//请求后拦截器
|
||||
instance.interceptors.response.use(
|
||||
(response) => {
|
||||
const { showLoading, errorCallback, showError = true, responseType } = response.config;
|
||||
if (showLoading && loading) {
|
||||
loading.close()
|
||||
}
|
||||
const responseData = response.data;
|
||||
if (responseType == "arraybuffer" || responseType == "blob") {
|
||||
return responseData;
|
||||
}
|
||||
//正常请求
|
||||
if (responseData.code == 200) {
|
||||
return responseData;
|
||||
} else if (responseData.code == 901) {
|
||||
//登录超时
|
||||
router.push("/login?redirectUrl=" + encodeURI(router.currentRoute.value.path));
|
||||
return Promise.reject({ showError: false, msg: "登录超时" });
|
||||
} else {
|
||||
//其他错误
|
||||
if (errorCallback) {
|
||||
errorCallback(responseData.info);
|
||||
}
|
||||
return Promise.reject({ showError: showError, msg: responseData.info });
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
if (error.config.showLoading && loading) {
|
||||
loading.close();
|
||||
}
|
||||
return Promise.reject({ showError: true, msg: "网络异常" })
|
||||
}
|
||||
);
|
||||
|
||||
const request = (config) => {
|
||||
const { url, params, dataType, showLoading = true, responseType = responseTypeJson } = config;
|
||||
let contentType = contentTypeForm;
|
||||
let formData = new FormData();// 创建form对象
|
||||
for (let key in params) {
|
||||
formData.append(key, params[key] == undefined ? "" : params[key]);
|
||||
}
|
||||
if (dataType != null && dataType == 'json') {
|
||||
contentType = contentTypeJson;
|
||||
}
|
||||
let headers = {
|
||||
'Content-Type': contentType,
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
}
|
||||
|
||||
return instance.post(url, formData, {
|
||||
onUploadProgress: (event) => {
|
||||
if (config.uploadProgressCallback) {
|
||||
config.uploadProgressCallback(event);
|
||||
}
|
||||
},
|
||||
responseType: responseType,
|
||||
headers: headers,
|
||||
showLoading: showLoading,
|
||||
errorCallback: config.errorCallback,
|
||||
showError: config.showError
|
||||
}).catch(error => {
|
||||
console.log(error);
|
||||
if (error.showError) {
|
||||
Message.error(error.msg);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
export default request;
|
||||
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
sizeToStr: (limit) => {
|
||||
var 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"
|
||||
}
|
||||
var sizeStr = size + ""; //转成字符串
|
||||
var index = sizeStr.indexOf("."); //获取小数点处的索引
|
||||
var dou = sizeStr.substr(index + 1, 2) //获取小数点后两位的值
|
||||
if (dou == "00") { //判断后两位是否为00,如果是则删除00
|
||||
return sizeStr.substring(0, index) + sizeStr.substr(index + 3, 2)
|
||||
}
|
||||
return size;
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
const regs = {
|
||||
email: /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/,
|
||||
number: /^([0]|[1-9][0-9]*)$/,
|
||||
password: /^(?=.*\d)(?=.*[a-zA-Z])[\da-zA-Z~!@#$%^&*_]{8,}$/,
|
||||
shareCode: /^[A-Za-z0-9]+$/
|
||||
}
|
||||
const verify = (rule, value, reg, callback) => {
|
||||
if (value) {
|
||||
if (reg.test(value)) {
|
||||
callback()
|
||||
} else {
|
||||
callback(new Error(rule.message))
|
||||
}
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
email: (rule, value, callback) => {
|
||||
return verify(rule, value, regs.email, callback)
|
||||
},
|
||||
number: (rule, value, callback) => {
|
||||
return verify(rule, value, regs.number, callback)
|
||||
},
|
||||
password: (rule, value, callback) => {
|
||||
return verify(rule, value, regs.password, callback)
|
||||
},
|
||||
shareCode: (rule, value, callback) => {
|
||||
return verify(rule, value, regs.shareCode, callback)
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,495 @@
|
||||
<template>
|
||||
<div class="framework">
|
||||
<div class="header">
|
||||
<div class="logo">
|
||||
<span class="iconfont icon-pan"></span>
|
||||
<span class="name">Small云盘</span>
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<el-popover
|
||||
:width="800"
|
||||
trigger="click"
|
||||
v-model:visible="showUploader"
|
||||
:offset="20"
|
||||
transition="none"
|
||||
:hide-after="0"
|
||||
:popper-style="{ padding: '0px' }"
|
||||
>
|
||||
<template #reference>
|
||||
<span class="iconfont icon-transfer"></span>
|
||||
</template>
|
||||
<template #default>
|
||||
<Uploader
|
||||
ref="uploaderRef"
|
||||
@uploadCallback="uploadCallbackHandler"
|
||||
></Uploader>
|
||||
</template>
|
||||
</el-popover>
|
||||
|
||||
<el-dropdown>
|
||||
<div class="user-info">
|
||||
<div class="avatar">
|
||||
<Avatar
|
||||
:userId="userInfo.userId"
|
||||
:avatar="userInfo.avatar"
|
||||
:timestamp="timestamp"
|
||||
:width="46"
|
||||
></Avatar>
|
||||
</div>
|
||||
<span class="nick-name">{{ userInfo.nickName }}</span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="updateAvatar" class="message-item">
|
||||
修改头像
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="updatePassword" class="message-item">
|
||||
修改密码
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout" class="message-item">
|
||||
退出
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="left-sider">
|
||||
<div class="menu-list">
|
||||
<div
|
||||
@click="jump(item)"
|
||||
:class="[
|
||||
'menu-item',
|
||||
item.menuCode == currentMenu.menuCode ? 'active' : '',
|
||||
]"
|
||||
v-for="item in menus"
|
||||
>
|
||||
<template v-if="item.allShow || (!item.allShow && userInfo.isAdmin)">
|
||||
<div :class="['iconfont', 'icon-' + item.icon]"></div>
|
||||
<div class="text">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-sub-list">
|
||||
<div
|
||||
@click="jump(sub)"
|
||||
:class="['menu-item-sub', currentPath == sub.path ? 'active' : '']"
|
||||
v-for="sub in currentMenu.children"
|
||||
>
|
||||
<span
|
||||
:class="['iconfont', 'icon-' + sub.icon]"
|
||||
v-if="sub.icon"
|
||||
></span>
|
||||
<span class="text">{{ sub.name }}</span>
|
||||
</div>
|
||||
<div class="tips" v-if="currentMenu && currentMenu.tips">
|
||||
{{ currentMenu.tips }}
|
||||
</div>
|
||||
<div class="space-info">
|
||||
<div>空间使用</div>
|
||||
<div class="percent">
|
||||
<el-progress
|
||||
:percentage="
|
||||
Math.floor(
|
||||
(useSpaceInfo.useSpace / useSpaceInfo.totalSpace) * 10000
|
||||
) / 100
|
||||
"
|
||||
color="#409eff"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-use">
|
||||
<div class="use">
|
||||
{{ proxy.Utils.sizeToStr(useSpaceInfo.useSpace) }}/
|
||||
{{ proxy.Utils.sizeToStr(useSpaceInfo.totalSpace) }}
|
||||
</div>
|
||||
<div class="iconfont icon-refresh" @click="getUseSpace"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body-content">
|
||||
<router-view v-slot="{ Component }">
|
||||
<component
|
||||
@addFile="addFile"
|
||||
ref="routerViewRef"
|
||||
:is="Component"
|
||||
@reload="getUseSpace"
|
||||
/>
|
||||
</router-view>
|
||||
</div>
|
||||
</div>
|
||||
<!--修改头像-->
|
||||
<UpdateAvatar
|
||||
ref="updateAvatarRef"
|
||||
@updateAvatar="reloadAvatar"
|
||||
></UpdateAvatar>
|
||||
<!--修改密码-->
|
||||
<UpdatePassword ref="updatePasswordRef"></UpdatePassword>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import UpdateAvatar from "./UpdateAvatar.vue";
|
||||
import UpdatePassword from "./UpdatePassword.vue";
|
||||
import Uploader from "@/views/main/Uploader.vue";
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
getCurrentInstance,
|
||||
watch,
|
||||
nextTick,
|
||||
computed,
|
||||
} from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const api = {
|
||||
getUseSpace: "/getUseSpace",
|
||||
logout: "/logout",
|
||||
};
|
||||
|
||||
const timestamp = ref(0);
|
||||
//获取用户信息
|
||||
const userInfo = ref(proxy.VueCookies.get("userInfo"));
|
||||
//显示上传窗口
|
||||
const showUploader = ref(false);
|
||||
|
||||
//添加文件
|
||||
const uploaderRef = ref();
|
||||
const addFile = (data) => {
|
||||
const { file, filePid } = data;
|
||||
showUploader.value = true;
|
||||
uploaderRef.value.addFile(file, filePid);
|
||||
};
|
||||
|
||||
//上传文件回调
|
||||
const routerViewRef = ref();
|
||||
const uploadCallbackHandler = () => {
|
||||
nextTick(() => {
|
||||
routerViewRef.value.reload();
|
||||
getUseSpace();
|
||||
});
|
||||
};
|
||||
|
||||
const menus = [
|
||||
{
|
||||
icon: "cloude",
|
||||
name: "首页",
|
||||
menuCode: "main",
|
||||
path: "/main/all",
|
||||
allShow: true,
|
||||
children: [
|
||||
{
|
||||
icon: "all",
|
||||
name: "全部",
|
||||
category: "all",
|
||||
path: "/main/all",
|
||||
},
|
||||
{
|
||||
icon: "video",
|
||||
name: "视频",
|
||||
category: "video",
|
||||
path: "/main/video",
|
||||
},
|
||||
{
|
||||
icon: "music",
|
||||
name: "音频",
|
||||
category: "music",
|
||||
path: "/main/music",
|
||||
},
|
||||
{
|
||||
icon: "image",
|
||||
name: "图片",
|
||||
category: "image",
|
||||
path: "/main/image",
|
||||
},
|
||||
{
|
||||
icon: "doc",
|
||||
name: "文档",
|
||||
category: "doc",
|
||||
path: "/main/doc",
|
||||
},
|
||||
{
|
||||
icon: "more",
|
||||
name: "其他",
|
||||
category: "others",
|
||||
path: "/main/others",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/myshare",
|
||||
icon: "share",
|
||||
name: "分享",
|
||||
menuCode: "share",
|
||||
allShow: true,
|
||||
children: [
|
||||
{
|
||||
name: "分享记录",
|
||||
path: "/myshare",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/recycle",
|
||||
icon: "del",
|
||||
name: "回收站",
|
||||
menuCode: "recycle",
|
||||
tips: "回收站为你保存10天内删除的文件",
|
||||
allShow: true,
|
||||
children: [
|
||||
{
|
||||
name: "删除的文件",
|
||||
path: "/recycle",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/settings/fileList",
|
||||
icon: "settings",
|
||||
name: "设置",
|
||||
menuCode: "settings",
|
||||
allShow: false,
|
||||
children: [
|
||||
{
|
||||
name: "用户文件",
|
||||
path: "/settings/fileList",
|
||||
},
|
||||
{
|
||||
name: "用户管理",
|
||||
path: "/settings/userList",
|
||||
},
|
||||
{
|
||||
path: "/settings/sysSetting",
|
||||
name: "系统设置",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const jump = (data) => {
|
||||
if (!data.path || data.menuCode == currentMenu.value.menuCode) {
|
||||
return;
|
||||
}
|
||||
router.push(data.path);
|
||||
};
|
||||
|
||||
const currentMenu = ref({});
|
||||
const currentPath = ref();
|
||||
|
||||
const setMenu = (menuCode, path) => {
|
||||
const menu = menus.find((item) => {
|
||||
return item.menuCode === menuCode;
|
||||
});
|
||||
currentMenu.value = menu;
|
||||
currentPath.value = path;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route,
|
||||
(newVal, oldVal) => {
|
||||
if (newVal.meta.menuCode) {
|
||||
setMenu(newVal.meta.menuCode, newVal.path);
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
|
||||
//使用空间
|
||||
const useSpaceInfo = ref({ useSpace: 0, totalSpace: 1 });
|
||||
const getUseSpace = async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.getUseSpace,
|
||||
showLoading: false,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
useSpaceInfo.value = result.data;
|
||||
};
|
||||
getUseSpace();
|
||||
|
||||
//修改头像
|
||||
const updateAvatarRef = ref();
|
||||
const updateAvatar = () => {
|
||||
updateAvatarRef.value.show(userInfo.value);
|
||||
};
|
||||
const reloadAvatar = () => {
|
||||
userInfo.value = proxy.VueCookies.get("userInfo");
|
||||
timestamp.value = new Date().getTime();
|
||||
};
|
||||
|
||||
//修改密码
|
||||
const updatePasswordRef = ref();
|
||||
const updatePassword = () => {
|
||||
updatePasswordRef.value.show();
|
||||
};
|
||||
|
||||
//退出登录
|
||||
const logout = () => {
|
||||
proxy.Confirm(`你确定要删除退出吗`, async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.logout,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
proxy.VueCookies.remove;
|
||||
router.push("/login");
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
box-shadow: 0 3px 10px 0 rgb(0 0 0 / 6%);
|
||||
height: 56px;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
position: relative;
|
||||
z-index: 200;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.icon-pan {
|
||||
font-size: 40px;
|
||||
color: #1296db;
|
||||
}
|
||||
.name {
|
||||
font-weight: bold;
|
||||
margin-left: 5px;
|
||||
font-size: 25px;
|
||||
color: #05a1f5;
|
||||
}
|
||||
}
|
||||
.right-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.icon-transfer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.user-info {
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
.avatar {
|
||||
margin: 0px 5px 0px 15px;
|
||||
}
|
||||
.nick-name {
|
||||
color: #05a1f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.body {
|
||||
display: flex;
|
||||
.left-sider {
|
||||
border-right: 1px solid #f1f2f4;
|
||||
display: flex;
|
||||
.menu-list {
|
||||
height: calc(100vh - 56px);
|
||||
width: 80px;
|
||||
box-shadow: 0 3px 10px 0 rgb(0 0 0 / 6%);
|
||||
border-right: 1px solid #f1f2f4;
|
||||
.menu-item {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
padding: 20px 0px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #f3f3f3;
|
||||
}
|
||||
.iconfont {
|
||||
font-weight: normal;
|
||||
font-size: 28px;
|
||||
}
|
||||
}
|
||||
.active {
|
||||
.iconfont {
|
||||
color: #06a7ff;
|
||||
}
|
||||
.text {
|
||||
color: #06a7ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.menu-sub-list {
|
||||
width: 200px;
|
||||
padding: 20px 10px 0px;
|
||||
position: relative;
|
||||
.menu-item-sub {
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background: #f3f3f3;
|
||||
}
|
||||
.iconfont {
|
||||
font-size: 14px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.text {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
.active {
|
||||
background: #eef9fe;
|
||||
.iconfont {
|
||||
color: #05a1f5;
|
||||
}
|
||||
.text {
|
||||
color: #05a1f5;
|
||||
}
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 10px;
|
||||
color: #888888;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.space-info {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
width: 100%;
|
||||
padding: 0px 5px;
|
||||
.percent {
|
||||
padding-right: 10px;
|
||||
}
|
||||
.space-use {
|
||||
margin-top: 5px;
|
||||
color: #7e7e7e;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
.use {
|
||||
flex: 1;
|
||||
}
|
||||
.iconfont {
|
||||
cursor: pointer;
|
||||
margin-right: 20px;
|
||||
color: #05a1f5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.body-content {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,541 @@
|
||||
<template>
|
||||
<div class="login-body">
|
||||
<div class="bg"></div>
|
||||
<div class="login-panel">
|
||||
<el-form
|
||||
class="login-register"
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
ref="formDataRef"
|
||||
>
|
||||
<div class="login-title">Small云盘</div>
|
||||
<!--input输入-->
|
||||
<el-form-item prop="email">
|
||||
<el-input
|
||||
size="large"
|
||||
clearable
|
||||
placeholder="请输入邮箱"
|
||||
v-model="formData.email"
|
||||
maxLength="150"
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-account"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<!--登录密码-->
|
||||
<el-form-item prop="password" v-if="opType == 1">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
placeholder="请输入密码"
|
||||
v-model="formData.password"
|
||||
show-password
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-password"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<!--注册-->
|
||||
<div v-if="opType == 0 || opType == 2">
|
||||
<el-form-item prop="emailCode">
|
||||
<div class="send-emali-panel">
|
||||
<el-input
|
||||
size="large"
|
||||
placeholder="请输入邮箱验证码"
|
||||
v-model="formData.emailCode"
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-checkcode"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button
|
||||
class="send-mail-btn"
|
||||
type="primary"
|
||||
size="large"
|
||||
@click="getEmailCode"
|
||||
>获取验证码</el-button
|
||||
>
|
||||
</div>
|
||||
<el-popover placement="left" :width="500" trigger="click">
|
||||
<div>
|
||||
<p>1、在垃圾箱中查找邮箱验证码</p>
|
||||
<p>2、在邮箱中头像->设置->反垃圾->白名单->设置邮件地址白名单</p>
|
||||
<p>
|
||||
3、将邮箱【laoluo@wuhancoder.com】添加到白名单不知道怎么设置?
|
||||
</p>
|
||||
</div>
|
||||
<template #reference>
|
||||
<span class="a-link" :style="{ 'font-size': '14px' }"
|
||||
>未收到邮箱验证码?</span
|
||||
>
|
||||
</template>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
<el-form-item prop="nickName" v-if="opType == 0">
|
||||
<el-input
|
||||
size="large"
|
||||
clearable
|
||||
placeholder="请输入昵称"
|
||||
v-model="formData.nickName"
|
||||
maxLength="20"
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-account"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="registerPassword">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
placeholder="请输入密码"
|
||||
v-model="formData.registerPassword"
|
||||
show-password
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-password"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="reRegisterPassword">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
placeholder="请再次输入密码"
|
||||
v-model="formData.reRegisterPassword"
|
||||
show-password
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-password"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item prop="checkCode">
|
||||
<div class="check-code-panel">
|
||||
<el-input
|
||||
size="large"
|
||||
placeholder="请输入验证码"
|
||||
v-model="formData.checkCode"
|
||||
@keyup.enter="doSubmit"
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-checkcode"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
<img
|
||||
:src="checkCodeUrl"
|
||||
class="check-code"
|
||||
@click="changeCheckCode(0)"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="opType == 1">
|
||||
<div class="rememberme-panel">
|
||||
<el-checkbox v-model="formData.rememberMe">记住我</el-checkbox>
|
||||
</div>
|
||||
<div class="no-account">
|
||||
<a href="javascript:void(0)" class="a-link" @click="showPanel(2)"
|
||||
>忘记密码?</a
|
||||
>
|
||||
<a href="javascript:void(0)" class="a-link" @click="showPanel(0)"
|
||||
>没有账号?</a
|
||||
>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="opType == 0">
|
||||
<a href="javascript:void(0)" class="a-link" @click="showPanel(1)"
|
||||
>已有账号?</a
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="opType == 2">
|
||||
<a href="javascript:void(0)" class="a-link" @click="showPanel(1)"
|
||||
>去登录?</a
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="op-btn"
|
||||
@click="doSubmit"
|
||||
size="large"
|
||||
>
|
||||
<span v-if="opType == 0">注册</span>
|
||||
<span v-if="opType == 1">登录</span>
|
||||
<span v-if="opType == 2">重置密码</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<!-- <div class="login-btn-qq" v-if="opType == 1">
|
||||
快捷登录 <img src="@/assets/qq.png" @click="qqLogin" />
|
||||
</div> -->
|
||||
</el-form>
|
||||
</div>
|
||||
<!--发送邮箱验证码-->
|
||||
<Dialog
|
||||
:show="dialogConfig4SendMailCode.show"
|
||||
:title="dialogConfig4SendMailCode.title"
|
||||
:buttons="dialogConfig4SendMailCode.buttons"
|
||||
width="500px"
|
||||
:showCancel="false"
|
||||
@close="dialogConfig4SendMailCode.show = false"
|
||||
>
|
||||
<el-form
|
||||
:model="formData4SendMailCode"
|
||||
:rules="rules"
|
||||
ref="formData4SendMailCodeRef"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item label="邮箱">
|
||||
{{ formData.email }}
|
||||
</el-form-item>
|
||||
<el-form-item label="验证码" prop="checkCode">
|
||||
<div class="check-code-panel">
|
||||
<el-input
|
||||
size="large"
|
||||
placeholder="请输入验证码"
|
||||
v-model="formData4SendMailCode.checkCode"
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-checkcode"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
<img
|
||||
:src="checkCodeUrl4SendMailCode"
|
||||
class="check-code"
|
||||
@click="changeCheckCode(1)"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, getCurrentInstance, nextTick, onMounted } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import md5 from "js-md5";
|
||||
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const api = {
|
||||
checkCode: "/api/checkCode",
|
||||
sendMailCode: "/sendEmailCode",
|
||||
register: "/register",
|
||||
login: "/login",
|
||||
resetPwd: "/resetPwd",
|
||||
qqlogin: "/qqlogin",
|
||||
};
|
||||
|
||||
// 0:注册 1:登录 2:重置密码
|
||||
const opType = ref();
|
||||
const showPanel = (type) => {
|
||||
opType.value = type;
|
||||
resetForm();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
showPanel(1);
|
||||
});
|
||||
|
||||
//验证码
|
||||
const checkCodeUrl = ref(api.checkCode);
|
||||
const checkCodeUrl4SendMailCode = ref(api.checkCode);
|
||||
const changeCheckCode = (type) => {
|
||||
if (type == 0) {
|
||||
checkCodeUrl.value =
|
||||
api.checkCode + "?type=" + type + "&time=" + new Date().getTime();
|
||||
} else {
|
||||
checkCodeUrl4SendMailCode.value =
|
||||
api.checkCode + "?type=" + type + "&time=" + new Date().getTime();
|
||||
}
|
||||
};
|
||||
|
||||
//发送邮箱验证码弹窗
|
||||
const formData4SendMailCode = ref({});
|
||||
const formData4SendMailCodeRef = ref();
|
||||
const dialogConfig4SendMailCode = reactive({
|
||||
show: false,
|
||||
title: "发送邮箱验证码",
|
||||
buttons: [
|
||||
{
|
||||
type: "primary",
|
||||
text: "发送验证码",
|
||||
click: () => {
|
||||
sendEmailCode();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
//获取邮箱验证码
|
||||
const getEmailCode = () => {
|
||||
formDataRef.value.validateField("email", (valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
dialogConfig4SendMailCode.show = true;
|
||||
|
||||
nextTick(() => {
|
||||
changeCheckCode(1);
|
||||
formData4SendMailCodeRef.value.resetFields();
|
||||
formData4SendMailCode.value = {
|
||||
email: formData.value.email,
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
//发送邮件
|
||||
const sendEmailCode = () => {
|
||||
formData4SendMailCodeRef.value.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
const params = Object.assign({}, formData4SendMailCode.value);
|
||||
params.type = opType.value == 0 ? 0 : 1;
|
||||
let result = await proxy.Request({
|
||||
url: api.sendMailCode,
|
||||
params: params,
|
||||
errorCallback: () => {
|
||||
changeCheckCode(1);
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
proxy.Message.success("验证码发送成功,请登录邮箱查看");
|
||||
dialogConfig4SendMailCode.show = false;
|
||||
});
|
||||
};
|
||||
|
||||
//登录,注册 弹出配置
|
||||
const dialogConfig = reactive({
|
||||
show: false,
|
||||
title: "标题",
|
||||
});
|
||||
|
||||
const checkRePassword = (rule, value, callback) => {
|
||||
if (value !== formData.value.registerPassword) {
|
||||
callback(new Error(rule.message));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
const formData = ref({});
|
||||
const formDataRef = ref();
|
||||
const rules = {
|
||||
email: [
|
||||
{ required: true, message: "请输入邮箱" },
|
||||
{ validator: proxy.Verify.email, message: "请输入正确的邮箱" },
|
||||
],
|
||||
password: [{ required: true, message: "请输入密码" }],
|
||||
emailCode: [{ required: true, message: "请输入邮箱验证码" }],
|
||||
nickName: [{ required: true, message: "请输入昵称" }],
|
||||
registerPassword: [
|
||||
{ required: true, message: "请输入密码" },
|
||||
{
|
||||
validator: proxy.Verify.password,
|
||||
message: "密码只能是数字,字母,特殊字符 8-18位",
|
||||
},
|
||||
],
|
||||
reRegisterPassword: [
|
||||
{ required: true, message: "请再次输入密码" },
|
||||
{
|
||||
validator: checkRePassword,
|
||||
message: "两次输入的密码不一致",
|
||||
},
|
||||
],
|
||||
checkCode: [{ required: true, message: "请输入图片验证码" }],
|
||||
};
|
||||
|
||||
//重置表单
|
||||
const resetForm = () => {
|
||||
dialogConfig.show = true;
|
||||
if (opType.value == 0) {
|
||||
dialogConfig.title = "注册";
|
||||
} else if (opType.value == 1) {
|
||||
dialogConfig.title = "登录";
|
||||
} else if (opType.value == 2) {
|
||||
dialogConfig.title = "重置密码";
|
||||
}
|
||||
nextTick(() => {
|
||||
changeCheckCode(0);
|
||||
formDataRef.value.resetFields();
|
||||
formData.value = {};
|
||||
|
||||
//登录
|
||||
if (opType.value == 1) {
|
||||
const cookieLoginInfo = proxy.VueCookies.get("loginInfo");
|
||||
if (cookieLoginInfo) {
|
||||
formData.value = cookieLoginInfo;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 登录、注册、重置密码 提交表单
|
||||
const doSubmit = () => {
|
||||
formDataRef.value.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
let params = {};
|
||||
Object.assign(params, formData.value);
|
||||
//注册
|
||||
if (opType.value == 0 || opType.value == 2) {
|
||||
params.password = params.registerPassword;
|
||||
delete params.registerPassword;
|
||||
delete params.reRegisterPassword;
|
||||
}
|
||||
//登录
|
||||
if (opType.value == 1) {
|
||||
let cookieLoginInfo = proxy.VueCookies.get("loginInfo");
|
||||
let cookiePassword =
|
||||
cookieLoginInfo == null ? null : cookieLoginInfo.password;
|
||||
if (params.password !== cookiePassword) {
|
||||
params.password = md5(params.password);
|
||||
}
|
||||
}
|
||||
let url = null;
|
||||
if (opType.value == 0) {
|
||||
url = api.register;
|
||||
} else if (opType.value == 1) {
|
||||
url = api.login;
|
||||
} else if (opType.value == 2) {
|
||||
url = api.resetPwd;
|
||||
}
|
||||
let result = await proxy.Request({
|
||||
url: url,
|
||||
params: params,
|
||||
errorCallback: () => {
|
||||
changeCheckCode(0);
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
//注册返回
|
||||
if (opType.value == 0) {
|
||||
proxy.Message.success("注册成功,请登录");
|
||||
showPanel(1);
|
||||
} else if (opType.value == 1) {
|
||||
//登录
|
||||
if (params.rememberMe) {
|
||||
const loginInfo = {
|
||||
email: params.email,
|
||||
password: params.password,
|
||||
rememberMe: params.rememberMe,
|
||||
};
|
||||
proxy.VueCookies.set("loginInfo", loginInfo, "7d");
|
||||
} else {
|
||||
proxy.VueCookies.remove("loginInfo");
|
||||
}
|
||||
dialogConfig.show = false;
|
||||
proxy.Message.success("登录成功");
|
||||
//存储cookie
|
||||
proxy.VueCookies.set("userInfo", result.data, 0);
|
||||
//重定向到原始页面
|
||||
const redirectUrl = route.query.redirectUrl || "/";
|
||||
router.push(redirectUrl);
|
||||
} else if (opType.value == 2) {
|
||||
//重置密码
|
||||
proxy.Message.success("重置密码成功,请登录");
|
||||
showPanel(1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogConfig.show = false;
|
||||
};
|
||||
|
||||
// //QQ登录
|
||||
// const qqLogin = async () => {
|
||||
// let result = await proxy.Request({
|
||||
// url: api.qqlogin,
|
||||
// params: {
|
||||
// callbackUrl: route.query.redirectUrl || "",
|
||||
// },
|
||||
// });
|
||||
// if (!result) {
|
||||
// return;
|
||||
// }
|
||||
// proxy.VueCookies.remove("userInfo");
|
||||
// document.location.href = result.data;
|
||||
// };
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-body {
|
||||
height: calc(100vh);
|
||||
background-size: cover;
|
||||
background: url("../assets/login_bg.jpg");
|
||||
display: flex;
|
||||
.bg {
|
||||
flex: 1;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-size: 800px;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url("../assets/login_img.png");
|
||||
}
|
||||
.login-panel {
|
||||
width: 430px;
|
||||
margin-right: 15%;
|
||||
margin-top: calc((100vh - 500px) / 2);
|
||||
.login-register {
|
||||
padding: 25px;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
.login-title {
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.send-emali-panel {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
.send-mail-btn {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
.rememberme-panel {
|
||||
width: 100%;
|
||||
}
|
||||
.no-account {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.op-btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.check-code-panel {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
.check-code {
|
||||
margin-left: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.login-btn-qq {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
img {
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<div>登录中,请勿刷新页面</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, getCurrentInstance, nextTick } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const api = {
|
||||
logincallback: "/qqlogin/callback",
|
||||
};
|
||||
const login = async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.logincallback,
|
||||
params: router.currentRoute.value.query,
|
||||
errorCallback: () => {
|
||||
router.push("/");
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
let redirectUrl = result.data.callbackUrl || "/";
|
||||
if (redirectUrl == "/login") {
|
||||
redirectUrl = "/";
|
||||
}
|
||||
proxy.VueCookies.set("userInfo", result.data.userInfo, 0);
|
||||
console.log("路径",redirectUrl);
|
||||
router.push(redirectUrl);
|
||||
};
|
||||
login();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<div>
|
||||
<Dialog
|
||||
:show="dialogConfig.show"
|
||||
:title="dialogConfig.title"
|
||||
:buttons="dialogConfig.buttons"
|
||||
width="500px"
|
||||
:showCancel="true"
|
||||
@close="dialogConfig.show = false"
|
||||
>
|
||||
<el-form
|
||||
:model="formData"
|
||||
ref="formDataRef"
|
||||
label-width="80px"
|
||||
@submit.prevent
|
||||
>
|
||||
<!--input输入-->
|
||||
<el-form-item label="昵称" prop="">
|
||||
{{ formData.nickName }}
|
||||
</el-form-item>
|
||||
<!--textarea输入-->
|
||||
<el-form-item label="头像" prop="">
|
||||
<AvatarUpload v-model="formData.avatar"></AvatarUpload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AvatarUpload from "@/components/AvatarUpload.vue";
|
||||
|
||||
import { ref, reactive, getCurrentInstance } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const api = {
|
||||
updateUserAvatar: "updateUserAvatar",
|
||||
};
|
||||
|
||||
const formData = ref({});
|
||||
const formDataRef = ref();
|
||||
|
||||
const show = (data) => {
|
||||
formData.value = Object.assign({}, data);
|
||||
formData.value.avatar = { userId: data.userId, qqAvatar: data.avatar };
|
||||
dialogConfig.value.show = true;
|
||||
};
|
||||
|
||||
defineExpose({ show });
|
||||
|
||||
const dialogConfig = ref({
|
||||
show: false,
|
||||
title: "修改头像",
|
||||
buttons: [
|
||||
{
|
||||
type: "primary",
|
||||
text: "确定",
|
||||
click: (e) => {
|
||||
submitForm();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const emit = defineEmits(["updateAvatar"]);
|
||||
const submitForm = async () => {
|
||||
if (!(formData.value.avatar instanceof File)) {
|
||||
dialogConfig.value.show = false;
|
||||
return;
|
||||
}
|
||||
let result = await proxy.Request({
|
||||
url: api.updateUserAvatar,
|
||||
params: {
|
||||
avatar: formData.value.avatar,
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
dialogConfig.value.show = false;
|
||||
const cookeUserInfo = proxy.VueCookies.get("userInfo");
|
||||
delete cookeUserInfo.avatar;
|
||||
proxy.VueCookies.set("userInfo", cookeUserInfo, 0);
|
||||
//更新cookie信息
|
||||
emit("updateAvatar");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
@@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div>
|
||||
<Dialog
|
||||
:show="dialogConfig.show"
|
||||
:title="dialogConfig.title"
|
||||
:buttons="dialogConfig.buttons"
|
||||
width="500px"
|
||||
:showCancel="true"
|
||||
@close="dialogConfig.show = false"
|
||||
>
|
||||
<el-form
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
ref="formDataRef"
|
||||
label-width="80px"
|
||||
@submit.prevent
|
||||
>
|
||||
<!--input输入-->
|
||||
<el-form-item label="新密码" prop="password">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
placeholder="请输入密码"
|
||||
v-model="formData.password"
|
||||
show-password
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-password"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<!--input输入-->
|
||||
<el-form-item label="确认密码" prop="rePassword">
|
||||
<el-input
|
||||
type="password"
|
||||
size="large"
|
||||
placeholder="请再次输入密码"
|
||||
v-model="formData.rePassword"
|
||||
show-password
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="iconfont icon-password"></span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import AvatarUpload from "@/components/AvatarUpload.vue";
|
||||
|
||||
import { ref, reactive, getCurrentInstance, nextTick } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const api = {
|
||||
updatePassword: "updatePassword",
|
||||
};
|
||||
|
||||
const formData = ref({});
|
||||
const formDataRef = ref();
|
||||
|
||||
const checkRePassword = (rule, value, callback) => {
|
||||
if (value !== formData.value.rePassword) {
|
||||
callback(new Error(rule.message));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const rules = {
|
||||
password: [
|
||||
{ required: true, message: "请输入密码" },
|
||||
{
|
||||
validator: proxy.Verify.password,
|
||||
message: "密码只能是数字,字母,特殊字符 8-18位",
|
||||
},
|
||||
],
|
||||
rePassword: [
|
||||
{ required: true, message: "请再次输入密码" },
|
||||
{
|
||||
validator: checkRePassword,
|
||||
message: "两次输入的密码不一致",
|
||||
},
|
||||
],
|
||||
};
|
||||
const show = () => {
|
||||
dialogConfig.value.show = true;
|
||||
nextTick(() => {
|
||||
formDataRef.value.resetFields();
|
||||
formData.value = {};
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({ show });
|
||||
|
||||
const dialogConfig = ref({
|
||||
show: false,
|
||||
title: "修改密码",
|
||||
buttons: [
|
||||
{
|
||||
type: "primary",
|
||||
text: "确定",
|
||||
click: (e) => {
|
||||
submitForm();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const submitForm = async () => {
|
||||
formDataRef.value.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
let result = await proxy.Request({
|
||||
url: api.updatePassword,
|
||||
params: {
|
||||
password: formData.value.password,
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
dialogConfig.value.show = false;
|
||||
proxy.message.success("密码修改成功");
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
||||
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="top">
|
||||
<div class="top-op">
|
||||
<div class="search-panel">
|
||||
<el-input
|
||||
clearable
|
||||
placeholder="输入文件名搜索"
|
||||
v-model="fileNameFuzzy"
|
||||
@keyup.enter="search"
|
||||
>
|
||||
<template #suffix>
|
||||
<i class="iconfont icon-search" @click="search"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="iconfont icon-refresh" @click="loadDataList"></div>
|
||||
<el-button
|
||||
:style="{ 'margin-left': '10px' }"
|
||||
type="danger"
|
||||
:disabled="selectFileIdList.length == 0"
|
||||
@click="delFileBatch"
|
||||
>
|
||||
<span class="iconfont icon-del"></span>
|
||||
批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
<!--导航-->
|
||||
<Navigation
|
||||
ref="navigationRef"
|
||||
@navChange="navChange"
|
||||
:adminShow="true"
|
||||
></Navigation>
|
||||
</div>
|
||||
<div class="file-list">
|
||||
<Table
|
||||
:columns="columns"
|
||||
:showPagination="true"
|
||||
:dataSource="tableData"
|
||||
:fetch="loadDataList"
|
||||
:initFetch="false"
|
||||
:options="tableOptions"
|
||||
@rowSelected="rowSelected"
|
||||
>
|
||||
<template #fileName="{ index, row }">
|
||||
<div
|
||||
class="file-item"
|
||||
@mouseenter="showOp(row)"
|
||||
@mouseleave="cancelShowOp(row)"
|
||||
>
|
||||
<template
|
||||
v-if="(row.fileType == 3 || row.fileType == 1) && row.status == 2"
|
||||
>
|
||||
<icon :cover="row.fileCover" :width="32"></icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
<icon v-if="row.folderType == 0" :fileType="row.fileType"></icon>
|
||||
<icon v-if="row.folderType == 1" :fileType="0"></icon>
|
||||
</template>
|
||||
<span class="file-name" v-if="!row.showEdit" :title="row.fileName">
|
||||
<span @click="preview(row)">{{ row.fileName }}</span>
|
||||
<span v-if="row.status == 0" class="transfer-status">转码中</span>
|
||||
<span v-if="row.status == 1" class="transfer-status transfer-fail"
|
||||
>转码失败</span
|
||||
>
|
||||
</span>
|
||||
<div class="edit-panel" v-if="row.showEdit">
|
||||
<el-input
|
||||
v-model.trim="row.fileNameReal"
|
||||
:maxLength="190"
|
||||
@keyup.enter="saveNameEdit(index)"
|
||||
>
|
||||
<template #suffix>{{ row.fileSuffix }}</template>
|
||||
</el-input>
|
||||
<span
|
||||
:class="[
|
||||
'iconfont icon-right1',
|
||||
row.fileNameReal ? '' : 'not-allow',
|
||||
]"
|
||||
@click="saveNameEdit(index)"
|
||||
></span>
|
||||
<span
|
||||
class="iconfont icon-error"
|
||||
@click="cancelNameEdit(index)"
|
||||
></span>
|
||||
</div>
|
||||
<span class="op">
|
||||
<template v-if="row.showOp && row.fileId">
|
||||
<span
|
||||
class="iconfont icon-download"
|
||||
@click="download(row)"
|
||||
v-if="row.folderType == 0"
|
||||
>下载</span
|
||||
>
|
||||
<span class="iconfont icon-del" @click="delFile(row)"
|
||||
>删除</span
|
||||
>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #fileSize="{ index, row }">
|
||||
<span v-if="row.fileSize">
|
||||
{{ proxy.Utils.sizeToStr(row.fileSize) }}</span
|
||||
>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
<!--预览-->
|
||||
<Preview ref="previewRef"> </Preview>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, getCurrentInstance, nextTick, computed } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const emit = defineEmits(["addFile"]);
|
||||
//添加文件
|
||||
const addFile = async (fileData) => {
|
||||
emit("addFile", { file: fileData.file, filePid: currentFolder.value.fileId });
|
||||
};
|
||||
//添加文件回调
|
||||
const reload = () => {
|
||||
showLoading.value = false;
|
||||
loadDataList();
|
||||
};
|
||||
defineExpose({
|
||||
reload,
|
||||
});
|
||||
|
||||
const api = {
|
||||
loadDataList: "/admin/loadFileList",
|
||||
delFile: "/admin/delFile",
|
||||
createDownloadUrl: "/admin/createDownloadUrl",
|
||||
download: "/api/admin/download",
|
||||
};
|
||||
|
||||
//列表
|
||||
const columns = [
|
||||
{
|
||||
label: "文件名",
|
||||
prop: "fileName",
|
||||
scopedSlots: "fileName",
|
||||
},
|
||||
{
|
||||
label: "发布人",
|
||||
prop: "nickName",
|
||||
width: 250,
|
||||
},
|
||||
{
|
||||
label: "修改时间",
|
||||
prop: "lastUpdateTime",
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
label: "大小",
|
||||
prop: "fileSize",
|
||||
scopedSlots: "fileSize",
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
//搜索
|
||||
const search = () => {
|
||||
showLoading.value = true;
|
||||
loadDataList();
|
||||
};
|
||||
//列表
|
||||
const tableData = ref({});
|
||||
const tableOptions = {
|
||||
extHeight: 50,
|
||||
selectType: "checkbox",
|
||||
};
|
||||
|
||||
//多选 批量选择
|
||||
const selectFileIdList = ref([]);
|
||||
const rowSelected = (rows) => {
|
||||
selectFileIdList.value = [];
|
||||
rows.forEach((item) => {
|
||||
selectFileIdList.value.push(item.userId + "_" + item.fileId);
|
||||
});
|
||||
};
|
||||
|
||||
const fileNameFuzzy = ref();
|
||||
const showLoading = ref(true);
|
||||
|
||||
const loadDataList = async () => {
|
||||
let params = {
|
||||
pageNo: tableData.value.pageNo,
|
||||
pageSize: tableData.value.pageSize,
|
||||
fileNameFuzzy: fileNameFuzzy.value,
|
||||
filePid: currentFolder.value.fileId,
|
||||
};
|
||||
let result = await proxy.Request({
|
||||
url: api.loadDataList,
|
||||
showLoading: showLoading,
|
||||
params,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
tableData.value = result.data;
|
||||
};
|
||||
|
||||
//展示操作按钮
|
||||
const showOp = (row) => {
|
||||
tableData.value.list.forEach((element) => {
|
||||
element.showOp = false;
|
||||
});
|
||||
row.showOp = true;
|
||||
};
|
||||
|
||||
const cancelShowOp = (row) => {
|
||||
row.showOp = false;
|
||||
};
|
||||
|
||||
const previewRef = ref();
|
||||
const navigationRef = ref();
|
||||
const preview = (data) => {
|
||||
if (data.folderType == 1) {
|
||||
navigationRef.value.openFolder(data);
|
||||
return;
|
||||
}
|
||||
if (data.status != 2) {
|
||||
proxy.Message.warning("文件正在转码中,无法预览");
|
||||
return;
|
||||
}
|
||||
previewRef.value.showPreview(data, 1);
|
||||
};
|
||||
|
||||
//目录
|
||||
const currentFolder = ref({ fileId: 0 });
|
||||
const navChange = (data) => {
|
||||
const { curFolder } = data;
|
||||
currentFolder.value = curFolder;
|
||||
showLoading.value = true;
|
||||
loadDataList();
|
||||
};
|
||||
|
||||
//删除文件
|
||||
const delFile = (row) => {
|
||||
proxy.Confirm(
|
||||
`你确定要删除【${row.fileName}】吗?删除的文件可在10天内通过回收站还原`,
|
||||
async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.delFile,
|
||||
params: {
|
||||
fileIdAndUserIds: row.userId + "_" + row.fileId,
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
loadDataList();
|
||||
}
|
||||
);
|
||||
};
|
||||
//批量删除
|
||||
const delFileBatch = () => {
|
||||
if (selectFileIdList.value.length == 0) {
|
||||
return;
|
||||
}
|
||||
proxy.Confirm(
|
||||
`你确定要删除这些文件吗?删除的文件可在10天内通过回收站还原`,
|
||||
async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.delFile,
|
||||
params: {
|
||||
fileIdAndUserIds: selectFileIdList.value.join(","),
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
loadDataList();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
//下载文件
|
||||
const download = async (row) => {
|
||||
let result = await proxy.Request({
|
||||
url: api.createDownloadUrl + "/" + row.userId + "/" + row.fileId,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
window.location.href = api.download + "/" + result.data;
|
||||
};
|
||||
|
||||
//分享
|
||||
const shareRef = ref();
|
||||
const share = (row) => {
|
||||
shareRef.value.show(row);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/file.list.scss";
|
||||
.search-panel {
|
||||
margin-left: 0px !important;
|
||||
}
|
||||
.file-list {
|
||||
margin-top: 10px;
|
||||
.file-item {
|
||||
.op {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div class="sys-setting-panel">
|
||||
<el-form
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
ref="formDataRef"
|
||||
label-width="150px"
|
||||
@submit.prevent
|
||||
>
|
||||
<!--input输入-->
|
||||
<el-form-item label="注册邮件标题" prop="registerEmailTitle">
|
||||
<el-input
|
||||
clearable
|
||||
placeholder="请输入注册邮件验证码邮件标题"
|
||||
v-model="formData.registerEmailTitle"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<!--textarea输入-->
|
||||
<el-form-item label="注册邮件标题" prop="registerEmailContent">
|
||||
<el-input
|
||||
clearable
|
||||
placeholder="请输入注册邮件验证码邮件内容%s占位符为验证码内容"
|
||||
v-model="formData.registerEmailContent"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="初始空间大小" prop="userInitUseSpace">
|
||||
<el-input
|
||||
clearable
|
||||
placeholder="初始空间大小"
|
||||
v-model="formData.userInitUseSpace"
|
||||
>
|
||||
<template #suffix>MB</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<!-- 单选 -->
|
||||
<el-form-item label="" prop="">
|
||||
<el-button type="primary" @click="saveSettings">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, getCurrentInstance } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const api = {
|
||||
getSysSettings: "/admin/getSysSettings",
|
||||
saveSettings: "/admin/saveSysSettings",
|
||||
};
|
||||
|
||||
const formData = ref({});
|
||||
const formDataRef = ref();
|
||||
const rules = {
|
||||
registerEmailTitle: [
|
||||
{ required: true, message: "请输入注册邮件验证码邮件标题" },
|
||||
],
|
||||
registerEmailContent: [
|
||||
{ required: true, message: "请输入注册邮件验证码邮件内容" },
|
||||
],
|
||||
userInitUseSpace: [
|
||||
{ required: true, message: "请输入初始化空间大小" },
|
||||
{
|
||||
validator: proxy.Verify.number,
|
||||
message: "空间大小只能是数字",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const getSysSettings = async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.getSysSettings,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
formData.value = result.data;
|
||||
};
|
||||
getSysSettings();
|
||||
|
||||
const saveSettings = async () => {
|
||||
formDataRef.value.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
let params = Object.assign({}, formData.value);
|
||||
let result = await proxy.Request({
|
||||
url: api.saveSettings,
|
||||
params: params,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
proxy.Message.success("保存成功");
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sys-setting-panel {
|
||||
margin-top: 20px;
|
||||
width: 600px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="top-panel">
|
||||
<el-form :model="searchFormData" label-width="80px" @submit.prevent>
|
||||
<el-row>
|
||||
<el-col :span="4">
|
||||
<!--input输入-->
|
||||
<el-form-item label="用户昵称">
|
||||
<el-input
|
||||
clearable
|
||||
placeholder="支持模糊搜索"
|
||||
v-model.trim="searchFormData.nickNameFuzzy"
|
||||
@keyup.native="loadDataList"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<!-- 下拉框 -->
|
||||
<el-form-item label="状态">
|
||||
<el-select
|
||||
clearable
|
||||
placeholder="请选择状态"
|
||||
v-model="searchFormData.status"
|
||||
>
|
||||
<el-option :value="1" label="启用"></el-option>
|
||||
<el-option :value="0" label="禁用"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4" :style="{ 'padding-left': '10px' }">
|
||||
<el-button type="primary" @click="loadDataList"> 查询 </el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="file-list">
|
||||
<Table
|
||||
:columns="columns"
|
||||
:showPagination="true"
|
||||
:dataSource="tableData"
|
||||
:fetch="loadDataList"
|
||||
:options="tableOptions"
|
||||
>
|
||||
<template #avatar="{ index, row }">
|
||||
<div class="avatar">
|
||||
<Avatar :userId="row.userId" :avatar="row.qqAvatar"></Avatar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #space="{ index, row }">
|
||||
{{ proxy.Utils.sizeToStr(row.useSpace) }}/{{
|
||||
proxy.Utils.sizeToStr(row.totalSpace)
|
||||
}}
|
||||
</template>
|
||||
|
||||
<template #status="{ index, row }">
|
||||
<span v-if="row.status == 1" style="color: #529b2e">启用</span>
|
||||
<span v-if="row.status == 0" style="color: #f56c6c">禁用</span>
|
||||
</template>
|
||||
<template #op="{ index, row }">
|
||||
<span class="a-link" @click="updateSpace(row)">分配空间</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span class="a-link" @click="updateUserStatus(row)">{{
|
||||
row.status == 0 ? "启用" : "禁用"
|
||||
}}</span>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
<Dialog
|
||||
:show="dialogConfig.show"
|
||||
:title="dialogConfig.title"
|
||||
:buttons="dialogConfig.buttons"
|
||||
width="400px"
|
||||
:showCancel="false"
|
||||
@close="dialogConfig.show = false"
|
||||
>
|
||||
<el-form
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
ref="formDataRef"
|
||||
label-width="80px"
|
||||
@submit.prevent
|
||||
>
|
||||
<!--input输入-->
|
||||
<el-form-item label="昵称">
|
||||
{{ formData.nickName }}
|
||||
</el-form-item>
|
||||
<el-form-item label="空间大小" prop="changeSpace">
|
||||
<el-input
|
||||
clearable
|
||||
placeholder="请输入空间大小"
|
||||
v-model="formData.changeSpace"
|
||||
>
|
||||
<template #suffix>MB</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, getCurrentInstance, nextTick } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const api = {
|
||||
loadDataList: "/admin/loadUserList",
|
||||
updateUserStatus: "/admin/updateUserStatus",
|
||||
updateUserSpace: "/admin/updateUserSpace",
|
||||
};
|
||||
|
||||
//列表
|
||||
const columns = [
|
||||
{
|
||||
label: "头像",
|
||||
prop: "avatar",
|
||||
width: 80,
|
||||
scopedSlots: "avatar",
|
||||
},
|
||||
{
|
||||
label: "昵称",
|
||||
prop: "nickName",
|
||||
},
|
||||
{
|
||||
label: "邮箱",
|
||||
prop: "email",
|
||||
},
|
||||
{
|
||||
label: "空间使用",
|
||||
prop: "space",
|
||||
scopedSlots: "space",
|
||||
},
|
||||
{
|
||||
label: "加入时间",
|
||||
prop: "joinTime",
|
||||
},
|
||||
{
|
||||
label: "最后登录时间",
|
||||
prop: "lastLoginTime",
|
||||
},
|
||||
{
|
||||
label: "状态",
|
||||
prop: "status",
|
||||
scopedSlots: "status",
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
label: "操作",
|
||||
prop: "op",
|
||||
width: 150,
|
||||
scopedSlots: "op",
|
||||
},
|
||||
];
|
||||
const searchFormData = ref({});
|
||||
|
||||
//列表
|
||||
const tableData = ref({});
|
||||
const tableOptions = {
|
||||
extHeight: 20,
|
||||
};
|
||||
const loadDataList = async () => {
|
||||
let params = {
|
||||
pageNo: tableData.value.pageNo,
|
||||
pageSize: tableData.value.pageSize,
|
||||
};
|
||||
Object.assign(params, searchFormData.value);
|
||||
let result = await proxy.Request({
|
||||
url: api.loadDataList,
|
||||
params,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
tableData.value = result.data;
|
||||
};
|
||||
|
||||
//修改状态
|
||||
const updateUserStatus = (row) => {
|
||||
proxy.Confirm(
|
||||
`你确定要【${row.status == 0 ? "启动" : "禁用"}】吗?`,
|
||||
async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.updateUserStatus,
|
||||
params: {
|
||||
userId: row.userId,
|
||||
status: row.status == 0 ? 1 : 0,
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
loadDataList();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
//分配空间大小
|
||||
const dialogConfig = ref({
|
||||
show: false,
|
||||
title: "修改空间大小",
|
||||
buttons: [
|
||||
{
|
||||
type: "primary",
|
||||
text: "确定",
|
||||
click: (e) => {
|
||||
submitForm();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const formData = ref({});
|
||||
const formDataRef = ref();
|
||||
const rules = {
|
||||
changeSpace: [{ required: true, message: "请输入空间大小" }],
|
||||
};
|
||||
|
||||
const updateSpace = (data) => {
|
||||
dialogConfig.value.show = true;
|
||||
nextTick(() => {
|
||||
formDataRef.value.resetFields();
|
||||
formData.value = Object.assign({}, data);
|
||||
});
|
||||
};
|
||||
|
||||
const submitForm = () => {
|
||||
formDataRef.value.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
let params = {};
|
||||
Object.assign(params, formData.value);
|
||||
let result = await proxy.Request({
|
||||
url: api.updateUserSpace,
|
||||
params: params,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
dialogConfig.value.show = false;
|
||||
proxy.Message.success("操作成功");
|
||||
loadDataList();
|
||||
});
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.top-panel {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 25px;
|
||||
overflow: hidden;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,511 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="top">
|
||||
<div class="top-op">
|
||||
<div class="btn">
|
||||
<el-upload
|
||||
:show-file-list="false"
|
||||
:with-credentials="true"
|
||||
:multiple="true"
|
||||
:http-request="addFile"
|
||||
:accept="fileAccept"
|
||||
>
|
||||
<el-button type="primary">
|
||||
<span class="iconfont icon-upload"></span>
|
||||
上传
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
<el-button type="success" @click="newFolder" v-if="category == 'all'">
|
||||
<span class="iconfont icon-folder-add"></span>
|
||||
新建文件夹
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="delFileBatch"
|
||||
type="danger"
|
||||
:disabled="selectFileIdList.length == 0"
|
||||
>
|
||||
<span class="iconfont icon-del"></span>
|
||||
批量删除
|
||||
</el-button>
|
||||
<el-button
|
||||
@click="moveFolderBatch"
|
||||
type="warning"
|
||||
:disabled="selectFileIdList.length == 0"
|
||||
>
|
||||
<span class="iconfont icon-move"></span>
|
||||
批量移动
|
||||
</el-button>
|
||||
<div class="search-panel">
|
||||
<el-input
|
||||
clearable
|
||||
placeholder="输入文件名搜索"
|
||||
v-model="fileNameFuzzy"
|
||||
@keyup.enter="search"
|
||||
>
|
||||
<template #suffix>
|
||||
<i class="iconfont icon-search" @click="search"></i>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="iconfont icon-refresh" @click="loadDataList"></div>
|
||||
</div>
|
||||
<!--导航-->
|
||||
<Navigation ref="navigationRef" @navChange="navChange"></Navigation>
|
||||
</div>
|
||||
<div class="file-list" v-if="tableData.list && tableData.list.length > 0">
|
||||
<Table
|
||||
ref="dataTableRef"
|
||||
:columns="columns"
|
||||
:showPagination="true"
|
||||
:dataSource="tableData"
|
||||
:fetch="loadDataList"
|
||||
:initFetch="false"
|
||||
:options="tableOptions"
|
||||
@rowSelected="rowSelected"
|
||||
>
|
||||
<template #fileName="{ index, row }">
|
||||
<div
|
||||
class="file-item"
|
||||
@mouseenter="showOp(row)"
|
||||
@mouseleave="cancelShowOp(row)"
|
||||
>
|
||||
<template
|
||||
v-if="(row.fileType == 3 || row.fileType == 1) && row.status == 2"
|
||||
>
|
||||
<icon :cover="row.fileCover" :width="32"></icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
<icon v-if="row.folderType == 0" :fileType="row.fileType"></icon>
|
||||
<icon v-if="row.folderType == 1" :fileType="0"></icon>
|
||||
</template>
|
||||
<span class="file-name" v-if="!row.showEdit" :title="row.fileName">
|
||||
<span @click="preview(row)">{{ row.fileName }}</span>
|
||||
<span v-if="row.status == 0" class="transfer-status">转码中</span>
|
||||
<span v-if="row.status == 1" class="transfer-status transfer-fail"
|
||||
>转码失败</span
|
||||
>
|
||||
</span>
|
||||
<div class="edit-panel" v-show="row.showEdit">
|
||||
<el-input
|
||||
v-model.trim="row.fileNameReal"
|
||||
ref="editNameRef"
|
||||
:maxLength="190"
|
||||
@keyup.enter="saveNameEdit(index)"
|
||||
>
|
||||
<template #suffix>{{ row.fileSuffix }}</template>
|
||||
</el-input>
|
||||
<span
|
||||
:class="[
|
||||
'iconfont icon-right1',
|
||||
row.fileNameReal ? '' : 'not-allow',
|
||||
]"
|
||||
@click="saveNameEdit(index)"
|
||||
></span>
|
||||
<span
|
||||
class="iconfont icon-error"
|
||||
@click="cancelNameEdit(index)"
|
||||
></span>
|
||||
</div>
|
||||
<span class="op">
|
||||
<template v-if="row.showOp && row.fileId && row.status == 2">
|
||||
<span class="iconfont icon-share1" @click="share(row)"
|
||||
>分享</span
|
||||
>
|
||||
<span
|
||||
class="iconfont icon-download"
|
||||
@click="download(row)"
|
||||
v-if="row.folderType == 0"
|
||||
>下载</span
|
||||
>
|
||||
<span class="iconfont icon-del" @click="delFile(row)"
|
||||
>删除</span
|
||||
>
|
||||
<span
|
||||
class="iconfont icon-edit"
|
||||
@click.stop="editFileName(index)"
|
||||
>重命名</span
|
||||
>
|
||||
<span class="iconfont icon-move" @click="moveFolder(row)"
|
||||
>移动</span
|
||||
>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #fileSize="{ index, row }">
|
||||
<span v-if="row.fileSize">
|
||||
{{ proxy.Utils.sizeToStr(row.fileSize) }}</span
|
||||
>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
<div class="no-data" v-else>
|
||||
<div class="no-data-inner">
|
||||
<Icon iconName="no_data" :width="120" fit="fill"></Icon>
|
||||
<div class="tips">当前目录为空,上传你的第一个文件吧</div>
|
||||
<div class="op-list">
|
||||
<el-upload
|
||||
:show-file-list="false"
|
||||
:with-credentials="true"
|
||||
:multiple="true"
|
||||
:http-request="addFile"
|
||||
:accept="fileAccept"
|
||||
>
|
||||
<div class="op-item">
|
||||
<Icon iconName="file" :width="60"></Icon>
|
||||
<div>上传文件</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
<div class="op-item" v-if="category == 'all'" @click="newFolder">
|
||||
<Icon iconName="folder" :width="60"></Icon>
|
||||
<div>新建目录</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--预览-->
|
||||
<Preview ref="previewRef"> </Preview>
|
||||
<!--移动-->
|
||||
<FolderSelect
|
||||
ref="folderSelectRef"
|
||||
@folderSelect="moveFolderDone"
|
||||
></FolderSelect>
|
||||
<!--分享-->
|
||||
<FileShare ref="shareRef"></FileShare>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import CategoryInfo from "@/js/CategoryInfo.js";
|
||||
import FileShare from "./ShareFile.vue";
|
||||
import { ref, reactive, getCurrentInstance, nextTick, computed } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const emit = defineEmits(["addFile"]);
|
||||
//添加文件
|
||||
const addFile = async (fileData) => {
|
||||
emit("addFile", { file: fileData.file, filePid: currentFolder.value.fileId });
|
||||
};
|
||||
//添加文件回调
|
||||
const reload = () => {
|
||||
showLoading.value = false;
|
||||
loadDataList();
|
||||
};
|
||||
defineExpose({
|
||||
reload,
|
||||
});
|
||||
const api = {
|
||||
loadDataList: "/file/loadDataList",
|
||||
rename: "/file/rename",
|
||||
newFoloder: "/file/newFoloder",
|
||||
getFolderInfo: "/file/getFolderInfo",
|
||||
delFile: "/file/delFile",
|
||||
changeFileFolder: "/file/changeFileFolder",
|
||||
createDownloadUrl: "/file/createDownloadUrl",
|
||||
download: "/api/file/download",
|
||||
};
|
||||
|
||||
const fileAccept = computed(() => {
|
||||
const categoryItem = CategoryInfo[category.value];
|
||||
return categoryItem ? categoryItem.accept : "*";
|
||||
});
|
||||
|
||||
//列表
|
||||
const columns = [
|
||||
{
|
||||
label: "文件名",
|
||||
prop: "fileName",
|
||||
scopedSlots: "fileName",
|
||||
},
|
||||
{
|
||||
label: "修改时间",
|
||||
prop: "lastUpdateTime",
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
label: "大小",
|
||||
prop: "fileSize",
|
||||
scopedSlots: "fileSize",
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
//搜索
|
||||
const search = () => {
|
||||
showLoading.value = true;
|
||||
loadDataList();
|
||||
};
|
||||
//列表
|
||||
const tableData = ref({});
|
||||
const tableOptions = {
|
||||
extHeight: 50,
|
||||
selectType: "checkbox",
|
||||
};
|
||||
|
||||
//多选 批量选择
|
||||
const selectFileIdList = ref([]);
|
||||
const rowSelected = (rows) => {
|
||||
selectFileIdList.value = [];
|
||||
rows.forEach((item) => {
|
||||
selectFileIdList.value.push(item.fileId);
|
||||
});
|
||||
};
|
||||
|
||||
const fileNameFuzzy = ref();
|
||||
const showLoading = ref(true);
|
||||
const category = ref();
|
||||
|
||||
const loadDataList = async () => {
|
||||
let params = {
|
||||
pageNo: tableData.value.pageNo,
|
||||
pageSize: tableData.value.pageSize,
|
||||
fileNameFuzzy: fileNameFuzzy.value,
|
||||
category: category.value,
|
||||
filePid: currentFolder.value.fileId,
|
||||
};
|
||||
if (params.category !== "all") {
|
||||
delete params.filePid;
|
||||
}
|
||||
let result = await proxy.Request({
|
||||
url: api.loadDataList,
|
||||
showLoading: showLoading,
|
||||
params,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
tableData.value = result.data;
|
||||
editing.value = false;
|
||||
};
|
||||
|
||||
//展示操作按钮
|
||||
const showOp = (row) => {
|
||||
tableData.value.list.forEach((element) => {
|
||||
element.showOp = false;
|
||||
});
|
||||
row.showOp = true;
|
||||
};
|
||||
|
||||
const cancelShowOp = (row) => {
|
||||
row.showOp = false;
|
||||
};
|
||||
|
||||
//编辑行
|
||||
const editing = ref(false);
|
||||
const editNameRef = ref();
|
||||
//新建文件夹
|
||||
const newFolder = () => {
|
||||
if (editing.value) {
|
||||
return;
|
||||
}
|
||||
tableData.value.list.forEach((element) => {
|
||||
element.showEdit = false;
|
||||
});
|
||||
editing.value = true;
|
||||
tableData.value.list.unshift({
|
||||
showEdit: true,
|
||||
fileType: 0,
|
||||
fileId: "",
|
||||
filePid: currentFolder.value.fileId,
|
||||
});
|
||||
nextTick(() => {
|
||||
editNameRef.value.focus();
|
||||
});
|
||||
};
|
||||
|
||||
//编辑文件名
|
||||
const editFileName = (index) => {
|
||||
if (tableData.value.list[0].fileId == "") {
|
||||
tableData.value.list.splice(0, 1);
|
||||
}
|
||||
tableData.value.list.forEach((element) => {
|
||||
element.showEdit = false;
|
||||
});
|
||||
let cureentData = tableData.value.list[index];
|
||||
cureentData.showEdit = true;
|
||||
|
||||
//编辑文件
|
||||
if (cureentData.folderType == 0) {
|
||||
cureentData.fileNameReal = cureentData.fileName.substring(
|
||||
0,
|
||||
cureentData.fileName.indexOf(".")
|
||||
);
|
||||
cureentData.fileSuffix = cureentData.fileName.substring(
|
||||
cureentData.fileName.indexOf(".")
|
||||
);
|
||||
} else {
|
||||
cureentData.fileNameReal = cureentData.fileName;
|
||||
cureentData.fileSuffix = "";
|
||||
}
|
||||
editing.value = true;
|
||||
nextTick(() => {
|
||||
editNameRef.value.focus();
|
||||
});
|
||||
};
|
||||
|
||||
const cancelNameEdit = (index) => {
|
||||
const fileData = tableData.value.list[index];
|
||||
if (fileData.fileId) {
|
||||
fileData.showEdit = false;
|
||||
} else {
|
||||
tableData.value.list.splice(index, 1);
|
||||
editing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const saveNameEdit = async (index) => {
|
||||
const { fileId, filePid, fileNameReal } = tableData.value.list[index];
|
||||
if (fileNameReal == "" || fileNameReal.indexOf("/") != -1) {
|
||||
proxy.Message.warning("文件名不能为空且不能含有斜杠");
|
||||
return;
|
||||
}
|
||||
let url = api.rename;
|
||||
if (fileId == "") {
|
||||
url = api.newFoloder;
|
||||
}
|
||||
let result = await proxy.Request({
|
||||
url: url,
|
||||
params: {
|
||||
fileId,
|
||||
filePid: filePid,
|
||||
fileName: fileNameReal,
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
tableData.value.list[index] = result.data;
|
||||
editing.value = false;
|
||||
};
|
||||
|
||||
const previewRef = ref();
|
||||
const navigationRef = ref();
|
||||
const preview = (data) => {
|
||||
if (data.folderType == 1) {
|
||||
//openFolder(data);
|
||||
navigationRef.value.openFolder(data);
|
||||
return;
|
||||
}
|
||||
if (data.status != 2) {
|
||||
proxy.Message.warning("文件正在转码中,无法预览");
|
||||
return;
|
||||
}
|
||||
previewRef.value.showPreview(data, 0);
|
||||
};
|
||||
|
||||
//目录
|
||||
const currentFolder = ref({ fileId: 0 });
|
||||
const navChange = (data) => {
|
||||
const { curFolder, categoryId } = data;
|
||||
currentFolder.value = curFolder;
|
||||
showLoading.value = true;
|
||||
category.value = categoryId;
|
||||
loadDataList();
|
||||
};
|
||||
|
||||
//移动目录
|
||||
const folderSelectRef = ref();
|
||||
const currentMoveFile = ref({});
|
||||
const moveFolder = (data) => {
|
||||
currentMoveFile.value = data;
|
||||
folderSelectRef.value.showFolderDialog(data.fileId);
|
||||
};
|
||||
|
||||
//批量删除
|
||||
const moveFolderBatch = () => {
|
||||
currentMoveFile.value = {};
|
||||
folderSelectRef.value.showFolderDialog(selectFileIdList.value.join(","));
|
||||
};
|
||||
|
||||
const moveFolderDone = async (folderId) => {
|
||||
if (
|
||||
currentMoveFile.value.filePid === folderId ||
|
||||
currentFolder.value.fileId == folderId
|
||||
) {
|
||||
proxy.Message.warning("文件正在当前目录,无需移动");
|
||||
return;
|
||||
}
|
||||
let filedIdsArray = [];
|
||||
if (currentMoveFile.value.fileId) {
|
||||
filedIdsArray.push(currentMoveFile.value.fileId);
|
||||
} else {
|
||||
filedIdsArray = filedIdsArray.concat(selectFileIdList.value);
|
||||
}
|
||||
let result = await proxy.Request({
|
||||
url: api.changeFileFolder,
|
||||
params: {
|
||||
fileIds: filedIdsArray.join(","),
|
||||
filePid: folderId,
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
folderSelectRef.value.close();
|
||||
loadDataList();
|
||||
};
|
||||
|
||||
//删除文件
|
||||
const delFile = (row) => {
|
||||
proxy.Confirm(
|
||||
`你确定要删除【${row.fileName}】吗?删除的文件可在10天内通过回收站还原`,
|
||||
async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.delFile,
|
||||
params: {
|
||||
fileIds: row.fileId,
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
loadDataList();
|
||||
dataTableRef.value.clearSelection();
|
||||
}
|
||||
);
|
||||
};
|
||||
//批量删除
|
||||
const delFileBatch = () => {
|
||||
if (selectFileIdList.value.length == 0) {
|
||||
return;
|
||||
}
|
||||
proxy.Confirm(
|
||||
`你确定要删除这些文件吗?删除的文件可在10天内通过回收站还原`,
|
||||
async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.delFile,
|
||||
params: {
|
||||
fileIds: selectFileIdList.value.join(","),
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
loadDataList();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
//下载文件
|
||||
const download = async (row) => {
|
||||
let result = await proxy.Request({
|
||||
url: api.createDownloadUrl + "/" + row.fileId,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
window.location.href = api.download + "/" + result.data;
|
||||
};
|
||||
|
||||
//分享
|
||||
const shareRef = ref();
|
||||
const share = (row) => {
|
||||
shareRef.value.show(row);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/file.list.scss";
|
||||
</style>
|
||||
@@ -0,0 +1,149 @@
|
||||
<template>
|
||||
<div>
|
||||
<Dialog
|
||||
:show="dialogConfig.show"
|
||||
:title="dialogConfig.title"
|
||||
:buttons="dialogConfig.buttons"
|
||||
width="600px"
|
||||
:showCancel="showCancel"
|
||||
@close="dialogConfig.show = false"
|
||||
>
|
||||
<el-form
|
||||
:model="formData"
|
||||
:rules="rules"
|
||||
ref="formDataRef"
|
||||
label-width="100px"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item label="文件"> {{ formData.fileName }} </el-form-item>
|
||||
<template v-if="showType == 0">
|
||||
<el-form-item label="有效期" prop="validType">
|
||||
<el-radio-group v-model="formData.validType">
|
||||
<el-radio :label="0">1天</el-radio>
|
||||
<el-radio :label="1">7天</el-radio>
|
||||
<el-radio :label="2">30天</el-radio>
|
||||
<el-radio :label="3">永久有效</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="提取码" prop="codeType">
|
||||
<el-radio-group v-model="formData.codeType">
|
||||
<el-radio :label="0">自定义</el-radio>
|
||||
<el-radio :label="1">系统生成</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item prop="code" v-if="formData.codeType == 0">
|
||||
<el-input
|
||||
clearable
|
||||
placeholder="请输入5位提取码"
|
||||
v-model.trim="formData.code"
|
||||
maxLength="5"
|
||||
:style="{ width: '130px' }"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-form-item label="分享连接" prop="validType">
|
||||
{{ shareUrl }}{{ resultInfo.shareId }}
|
||||
</el-form-item>
|
||||
<el-form-item label="提取码" prop="validType">
|
||||
{{ resultInfo.code }}
|
||||
</el-form-item>
|
||||
<el-form-item prop="validType">
|
||||
<el-button type="primary" @click="copy">复制链接极提取码</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import useClipboard from "vue-clipboard3";
|
||||
const { toClipboard } = useClipboard();
|
||||
import { ref, getCurrentInstance, nextTick } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
|
||||
const shareUrl = ref(document.location.origin + "/share/");
|
||||
|
||||
const api = {
|
||||
shareFile: "/share/shareFile",
|
||||
};
|
||||
const showType = ref(0);
|
||||
const formData = ref({});
|
||||
const formDataRef = ref();
|
||||
const rules = {
|
||||
validType: [{ required: true, message: "请选择有效期" }],
|
||||
codeType: [{ required: true, message: "请选择提取码类型" }],
|
||||
code: [
|
||||
{ required: true, message: "请输入提取码" },
|
||||
{ validator: proxy.Verify.shareCode, message: "提取码只能是数字字母" },
|
||||
{ min: 5, message: "提取码最少5位" },
|
||||
],
|
||||
};
|
||||
|
||||
const showCancel = ref(true);
|
||||
const dialogConfig = ref({
|
||||
show: false,
|
||||
title: "分享",
|
||||
buttons: [
|
||||
{
|
||||
type: "primary",
|
||||
text: "确定",
|
||||
click: (e) => {
|
||||
share();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const resultInfo = ref({});
|
||||
const share = async () => {
|
||||
if (Object.keys(resultInfo.value).length > 0) {
|
||||
dialogConfig.value.show = false;
|
||||
return;
|
||||
}
|
||||
formDataRef.value.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
let params = {};
|
||||
Object.assign(params, formData.value);
|
||||
let result = await proxy.Request({
|
||||
url: api.shareFile,
|
||||
params: params,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
showType.value = 1;
|
||||
resultInfo.value = result.data;
|
||||
dialogConfig.value.buttons[0].text = "关闭";
|
||||
showCancel.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const show = (data) => {
|
||||
showCancel.value = true;
|
||||
dialogConfig.value.show = true;
|
||||
showType.value = 0;
|
||||
resultInfo.value = {};
|
||||
nextTick(() => {
|
||||
formDataRef.value.resetFields();
|
||||
formData.value = Object.assign({}, data);
|
||||
});
|
||||
};
|
||||
|
||||
defineExpose({ show });
|
||||
|
||||
const copy = async () => {
|
||||
await toClipboard(
|
||||
`链接:${shareUrl.value}${resultInfo.value.shareId} 提取码: ${resultInfo.value.code}`
|
||||
);
|
||||
proxy.Message.success("复制成功");
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,455 @@
|
||||
<template>
|
||||
<div class="uploader-panel">
|
||||
<div class="uploader-title">
|
||||
<span>上传任务</span>
|
||||
<span class="tips">(仅展示本次上传任务)</span>
|
||||
</div>
|
||||
<div class="file-list">
|
||||
<div v-for="(item, index) in fileList" class="file-item">
|
||||
<div class="upload-panel">
|
||||
<div class="file-name">
|
||||
{{ item.fileName }}
|
||||
</div>
|
||||
<div class="progress">
|
||||
<!--上传-->
|
||||
<el-progress
|
||||
:percentage="item.uploadProgress"
|
||||
v-if="
|
||||
item.status == STATUS.uploading.value ||
|
||||
item.status == STATUS.upload_seconds.value ||
|
||||
item.status == STATUS.upload_finish.value
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="upload-status">
|
||||
<!--图标-->
|
||||
<span
|
||||
:class="['iconfont', 'icon-' + STATUS[item.status].icon]"
|
||||
:style="{ color: STATUS[item.status].color }"
|
||||
></span>
|
||||
<!--状态描述-->
|
||||
<span
|
||||
class="status"
|
||||
:style="{ color: STATUS[item.status].color }"
|
||||
>{{
|
||||
item.status == "fail" ? item.errorMsg : STATUS[item.status].desc
|
||||
}}</span
|
||||
>
|
||||
<!--上传中-->
|
||||
<span
|
||||
class="upload-info"
|
||||
v-if="item.status == STATUS.uploading.value"
|
||||
>
|
||||
{{ sizeTostr(item.uploadSize) }}/{{ sizeTostr(item.totalSize) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="op">
|
||||
<!--MD5-->
|
||||
<el-progress
|
||||
type="circle"
|
||||
:width="50"
|
||||
:percentage="item.md5Progress"
|
||||
v-if="item.status == STATUS.init.value"
|
||||
/>
|
||||
<div class="op-btn">
|
||||
<span v-if="item.status === STATUS.uploading.value">
|
||||
<icon
|
||||
:width="28"
|
||||
class="btn-item"
|
||||
iconName="upload"
|
||||
v-if="item.pause"
|
||||
title="上传"
|
||||
@click="startUpload(item.uid)"
|
||||
></icon>
|
||||
<icon
|
||||
:width="28"
|
||||
class="btn-item"
|
||||
iconName="pause"
|
||||
title="暂停"
|
||||
@click="pauseUpload(item.uid)"
|
||||
v-else
|
||||
></icon>
|
||||
</span>
|
||||
<icon
|
||||
:width="28"
|
||||
class="del btn-item"
|
||||
iconName="del"
|
||||
title="删除"
|
||||
v-if="
|
||||
item.status != STATUS.init.value &&
|
||||
item.status != STATUS.upload_finish.value &&
|
||||
item.status != STATUS.upload_seconds.value
|
||||
"
|
||||
@click="delUpload(item.uid, index)"
|
||||
></icon>
|
||||
<icon
|
||||
:width="28"
|
||||
class="clean btn-item"
|
||||
iconName="clean"
|
||||
title="清除"
|
||||
v-if="
|
||||
item.status == STATUS.upload_finish.value ||
|
||||
item.status == STATUS.upload_seconds.value
|
||||
"
|
||||
@click="delUpload(item.uid, index)"
|
||||
></icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="fileList.length == 0">
|
||||
<NoData msg="暂无上传任务"></NoData>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
getCurrentInstance,
|
||||
onMounted,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
nextTick,
|
||||
} from "vue";
|
||||
import SparkMD5 from "spark-md5";
|
||||
const { proxy } = getCurrentInstance();
|
||||
|
||||
const STATUS = {
|
||||
emptyfile: {
|
||||
value: "emptyfile",
|
||||
desc: "文件为空",
|
||||
color: "#F75000",
|
||||
icon: "close",
|
||||
},
|
||||
fail: {
|
||||
value: "fail",
|
||||
desc: "上传失败",
|
||||
color: "#F75000",
|
||||
icon: "close",
|
||||
},
|
||||
init: {
|
||||
value: "init",
|
||||
desc: "解析中",
|
||||
color: "#e6a23c",
|
||||
icon: "clock",
|
||||
},
|
||||
uploading: {
|
||||
value: "uploading",
|
||||
desc: "上传中",
|
||||
color: "#409eff",
|
||||
icon: "upload",
|
||||
},
|
||||
upload_finish: {
|
||||
value: "upload_finish",
|
||||
desc: "上传完成",
|
||||
color: "#67c23a",
|
||||
icon: "ok",
|
||||
},
|
||||
upload_seconds: {
|
||||
value: "upload_seconds",
|
||||
desc: "秒传",
|
||||
color: "#67c23a",
|
||||
icon: "ok",
|
||||
},
|
||||
};
|
||||
|
||||
const chunkSize = 1024 * 1024 * 5;
|
||||
const fileList = ref([]);
|
||||
const delList = ref([]);
|
||||
|
||||
const addFile = async (file, filePid) => {
|
||||
const fileItem = {
|
||||
file: file,
|
||||
//文件UID
|
||||
uid: file.uid,
|
||||
//md5进度
|
||||
md5Progress: 0,
|
||||
//md5值
|
||||
md5: null,
|
||||
//文件名
|
||||
fileName: file.name,
|
||||
//上传状态
|
||||
status: STATUS.init.value,
|
||||
//已上传大小
|
||||
uploadSize: 0,
|
||||
//文件总大小
|
||||
totalSize: file.size,
|
||||
//进度
|
||||
uploadProgress: 0,
|
||||
//暂停
|
||||
pause: false,
|
||||
//当前分片
|
||||
chunkIndex: 0,
|
||||
//父级ID
|
||||
filePid: filePid,
|
||||
//错误信息
|
||||
errorMsg: null,
|
||||
};
|
||||
//加入文件
|
||||
fileList.value.unshift(fileItem);
|
||||
if (fileItem.totalSize == 0) {
|
||||
fileItem.status = STATUS.emptyfile.value;
|
||||
return;
|
||||
}
|
||||
//文件MD5
|
||||
let md5FileUid = await computeMD5(fileItem);
|
||||
if (md5FileUid == null) {
|
||||
return;
|
||||
}
|
||||
uploadFile(md5FileUid);
|
||||
};
|
||||
defineExpose({ addFile });
|
||||
|
||||
//开始上传
|
||||
const startUpload = (uid) => {
|
||||
let currentFile = getFileByUid(uid);
|
||||
currentFile.pause = false;
|
||||
uploadFile(uid, currentFile.chunkIndex);
|
||||
};
|
||||
//暂停上传
|
||||
const pauseUpload = (uid) => {
|
||||
let currentFile = getFileByUid(uid);
|
||||
currentFile.pause = true;
|
||||
};
|
||||
//删除文件
|
||||
const delUpload = (uid, index) => {
|
||||
delList.value.push(uid);
|
||||
fileList.value.splice(index, 1);
|
||||
};
|
||||
|
||||
const emit = defineEmits(["uploadCallback"]);
|
||||
const uploadFile = async (uid, chunkIndex) => {
|
||||
chunkIndex = chunkIndex ? chunkIndex : 0;
|
||||
//分片上传
|
||||
let currentFile = getFileByUid(uid);
|
||||
const file = currentFile.file;
|
||||
const fileSize = currentFile.totalSize;
|
||||
const chunks = Math.ceil(fileSize / chunkSize);
|
||||
for (let i = chunkIndex; i < chunks; i++) {
|
||||
let delIndex = delList.value.indexOf(uid);
|
||||
if (delIndex != -1) {
|
||||
delList.value.splice(delIndex, 1);
|
||||
// console.log(delList.value);
|
||||
break;
|
||||
}
|
||||
currentFile = getFileByUid(uid);
|
||||
if (currentFile.pause) {
|
||||
break;
|
||||
}
|
||||
let start = i * chunkSize;
|
||||
let end = start + chunkSize >= fileSize ? fileSize : start + chunkSize;
|
||||
let chunkFile = file.slice(start, end);
|
||||
let uploadResult = await proxy.Request({
|
||||
url: "/file/uploadFile",
|
||||
showLoading: false,
|
||||
dataType: "file",
|
||||
params: {
|
||||
file: chunkFile,
|
||||
fileName: file.name,
|
||||
fileMd5: currentFile.md5,
|
||||
chunkIndex: i,
|
||||
chunks: chunks,
|
||||
fileId: currentFile.fileId,
|
||||
filePid: currentFile.filePid,
|
||||
},
|
||||
showError: false,
|
||||
errorCallback: (errorMsg) => {
|
||||
currentFile.status = STATUS.fail.value;
|
||||
currentFile.errorMsg = errorMsg;
|
||||
},
|
||||
uploadProgressCallback: (event) => {
|
||||
let loaded = event.loaded;
|
||||
if (loaded > fileSize) {
|
||||
loaded = fileSize;
|
||||
}
|
||||
currentFile.uploadSize = i * chunkSize + loaded;
|
||||
currentFile.uploadProgress = Math.floor(
|
||||
(currentFile.uploadSize / fileSize) * 100
|
||||
);
|
||||
},
|
||||
});
|
||||
if (uploadResult == null) {
|
||||
break;
|
||||
}
|
||||
currentFile.fileId = uploadResult.data.fileId;
|
||||
currentFile.status = STATUS[uploadResult.data.status].value;
|
||||
currentFile.chunkIndex = i;
|
||||
if (
|
||||
uploadResult.data.status == STATUS.upload_seconds.value ||
|
||||
uploadResult.data.status == STATUS.upload_finish.value
|
||||
) {
|
||||
currentFile.uploadProgress = 100;
|
||||
emit("uploadCallback");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const computeMD5 = (fileItem) => {
|
||||
let file = fileItem.file;
|
||||
let blobSlice =
|
||||
File.prototype.slice ||
|
||||
File.prototype.mozSlice ||
|
||||
File.prototype.webkitSlice;
|
||||
let chunks = Math.ceil(file.size / chunkSize);
|
||||
let currentChunk = 0;
|
||||
let spark = new SparkMD5.ArrayBuffer();
|
||||
let fileReader = new FileReader();
|
||||
let time = new Date().getTime();
|
||||
//file.cmd5 = true;
|
||||
|
||||
let loadNext = () => {
|
||||
let start = currentChunk * chunkSize;
|
||||
let end = start + chunkSize >= file.size ? file.size : start + chunkSize;
|
||||
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
|
||||
};
|
||||
|
||||
loadNext();
|
||||
return new Promise((resolve, reject) => {
|
||||
let resultFile = getFileByUid(file.uid);
|
||||
fileReader.onload = (e) => {
|
||||
spark.append(e.target.result); // Append array buffer
|
||||
currentChunk++;
|
||||
if (currentChunk < chunks) {
|
||||
/* console.log(
|
||||
`第${file.name},${currentChunk}分片解析完成, 开始第${
|
||||
currentChunk + 1
|
||||
} / ${chunks}分片解析`
|
||||
); */
|
||||
let percent = Math.floor((currentChunk / chunks) * 100);
|
||||
resultFile.md5Progress = percent;
|
||||
loadNext();
|
||||
} else {
|
||||
let md5 = spark.end();
|
||||
/* console.log(
|
||||
`MD5计算完成:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${
|
||||
file.size
|
||||
} 用时:${new Date().getTime() - time} ms`
|
||||
); */
|
||||
spark.destroy(); //释放缓存
|
||||
resultFile.md5Progress = 100;
|
||||
resultFile.status = STATUS.uploading.value;
|
||||
resultFile.md5 = md5;
|
||||
resolve(fileItem.uid);
|
||||
}
|
||||
};
|
||||
fileReader.onerror = () => {
|
||||
resultFile.md5Progress = -1;
|
||||
resultFile.status = STATUS.fail.value;
|
||||
resolve(fileItem.uid);
|
||||
};
|
||||
}).catch((error) => {
|
||||
return null;
|
||||
});
|
||||
};
|
||||
|
||||
//获取文件
|
||||
const getFileByUid = (uid) => {
|
||||
let file = fileList.value.find((item) => {
|
||||
return item.file.uid === uid;
|
||||
});
|
||||
return file;
|
||||
};
|
||||
|
||||
//字节转为M
|
||||
const sizeTostr = (size) => {
|
||||
var data = "";
|
||||
if (size < 0.1 * 1024) {
|
||||
//如果小于0.1KB转化成B
|
||||
data = size.toFixed(2) + "B";
|
||||
} else if (size < 0.1 * 1024 * 1024) {
|
||||
//如果小于0.1MB转化成KB
|
||||
data = (size / 1024).toFixed(2) + "KB";
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
//如果小于1GB转化成MB
|
||||
data = (size / (1024 * 1024)).toFixed(2) + "MB";
|
||||
} else {
|
||||
//其他转化成GB
|
||||
data = (size / (1024 * 1024 * 1024)).toFixed(2) + "GB";
|
||||
}
|
||||
var sizestr = data + "";
|
||||
var len = sizestr.indexOf(".");
|
||||
var dec = sizestr.substr(len + 1, 2);
|
||||
if (dec == "00") {
|
||||
//当小数点后为00时 去掉小数部分
|
||||
return sizestr.substring(0, len) + sizestr.substr(len + 3, 2);
|
||||
}
|
||||
return sizestr;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.uploader-panel {
|
||||
.uploader-title {
|
||||
border-bottom: 1px solid #ddd;
|
||||
line-height: 40px;
|
||||
padding: 0px 10px;
|
||||
font-size: 15px;
|
||||
.tips {
|
||||
font-size: 13px;
|
||||
color: rgb(169, 169, 169);
|
||||
}
|
||||
}
|
||||
.file-list {
|
||||
overflow: auto;
|
||||
padding: 10px 0px;
|
||||
min-height: calc(100vh / 2);
|
||||
max-height: calc(100vh - 120px);
|
||||
.file-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 3px 10px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.file-item:nth-child(even) {
|
||||
background-color: #fcf8f4;
|
||||
}
|
||||
.upload-panel {
|
||||
flex: 1;
|
||||
.file-name {
|
||||
color: rgb(64, 62, 62);
|
||||
}
|
||||
.upload-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 5px;
|
||||
.iconfont {
|
||||
margin-right: 3px;
|
||||
}
|
||||
.status {
|
||||
color: red;
|
||||
font-size: 13px;
|
||||
}
|
||||
.upload-info {
|
||||
margin-left: 5px;
|
||||
font-size: 12px;
|
||||
color: rgb(112, 111, 111);
|
||||
}
|
||||
}
|
||||
.progress {
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
.op {
|
||||
width: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
.op-btn {
|
||||
.btn-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
.del,
|
||||
.clean {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="top">
|
||||
<el-button
|
||||
type="success"
|
||||
:disabled="selectFileIdList.length == 0"
|
||||
@click="revertBatch"
|
||||
>
|
||||
<span class="iconfont icon-revert"></span>还原
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
:disabled="selectFileIdList.length == 0"
|
||||
@click="delBatch"
|
||||
>
|
||||
<span class="iconfont icon-del"></span>批量删除
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="file-list">
|
||||
<Table
|
||||
:columns="columns"
|
||||
:showPagination="true"
|
||||
:dataSource="tableData"
|
||||
:fetch="loadDataList"
|
||||
:options="tableOptions"
|
||||
@rowSelected="rowSelected"
|
||||
>
|
||||
<template #fileName="{ index, row }">
|
||||
<div
|
||||
class="file-item"
|
||||
@mouseenter="showOp(row)"
|
||||
@mouseleave="cancelShowOp(row)"
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
(row.fileType == 3 || row.fileType == 1) && row.status !== 0
|
||||
"
|
||||
>
|
||||
<icon :cover="row.fileCover"></icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
<icon v-if="row.folderType == 0" :fileType="row.fileType"></icon>
|
||||
<icon v-if="row.folderType == 1" :fileType="0"></icon>
|
||||
</template>
|
||||
<span class="file-name" :title="row.fileName">
|
||||
<span>{{ row.fileName }}</span>
|
||||
</span>
|
||||
<span class="op">
|
||||
<template v-if="row.showOp && row.fileId">
|
||||
<span class="iconfont icon-revert" @click="revert(row)"
|
||||
>还原</span
|
||||
>
|
||||
<span class="iconfont icon-del" @click="delFile(row)"
|
||||
>删除</span
|
||||
>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #fileSize="{ index, row }">
|
||||
<span v-if="row.fileSize">
|
||||
{{ proxy.Utils.sizeToStr(row.fileSize) }}</span
|
||||
>
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, getCurrentInstance, nextTick } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const api = {
|
||||
loadDataList: "/recycle/loadRecycleList",
|
||||
delFile: "/recycle/delFile",
|
||||
recoverFile: "/recycle/recoverFile",
|
||||
};
|
||||
|
||||
//列表
|
||||
const columns = [
|
||||
{
|
||||
label: "文件名",
|
||||
prop: "fileName",
|
||||
scopedSlots: "fileName",
|
||||
},
|
||||
{
|
||||
label: "删除时间",
|
||||
prop: "recoveryTime",
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
label: "大小",
|
||||
prop: "fileSize",
|
||||
scopedSlots: "fileSize",
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
//列表
|
||||
const tableData = ref({});
|
||||
const tableOptions = {
|
||||
extHeight: 20,
|
||||
selectType: "checkbox",
|
||||
};
|
||||
const loadDataList = async () => {
|
||||
let params = {
|
||||
pageNo: tableData.value.pageNo,
|
||||
pageSize: tableData.value.pageSize,
|
||||
};
|
||||
if (params.category !== "all") {
|
||||
delete params.filePid;
|
||||
}
|
||||
let result = await proxy.Request({
|
||||
url: api.loadDataList,
|
||||
params,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
tableData.value = result.data;
|
||||
};
|
||||
|
||||
//展示操作按钮
|
||||
const showOp = (row) => {
|
||||
tableData.value.list.forEach((element) => {
|
||||
element.showOp = false;
|
||||
});
|
||||
row.showOp = true;
|
||||
};
|
||||
|
||||
const cancelShowOp = (row) => {
|
||||
row.showOp = false;
|
||||
};
|
||||
|
||||
const selectFileIdList = ref([]);
|
||||
const rowSelected = (rows) => {
|
||||
selectFileIdList.value = [];
|
||||
rows.forEach((item) => {
|
||||
selectFileIdList.value.push(item.fileId);
|
||||
});
|
||||
};
|
||||
|
||||
//恢复
|
||||
const revert = (row) => {
|
||||
proxy.Confirm(`你确定要还原【${row.fileName}】吗?`, async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.recoverFile,
|
||||
params: {
|
||||
fileIds: row.fileId,
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
loadDataList();
|
||||
});
|
||||
};
|
||||
|
||||
const revertBatch = () => {
|
||||
if (selectFileIdList.value.length == 0) {
|
||||
return;
|
||||
}
|
||||
proxy.Confirm(`你确定要还原这些文件吗?`, async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.recoverFile,
|
||||
params: {
|
||||
fileIds: selectFileIdList.value.join(","),
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
loadDataList();
|
||||
});
|
||||
};
|
||||
//删除文件
|
||||
const emit = defineEmits(["reload"]);
|
||||
const delFile = (row) => {
|
||||
proxy.Confirm(`你确定要删除【${row.fileName}】?`, async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.delFile,
|
||||
params: {
|
||||
fileIds: row.fileId,
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
loadDataList();
|
||||
emit("reload");
|
||||
});
|
||||
};
|
||||
|
||||
const delBatch = (row) => {
|
||||
if (selectFileIdList.value.length == 0) {
|
||||
return;
|
||||
}
|
||||
proxy.Confirm(`你确定要删除选中的文件?删除将无法恢复`, async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.delFile,
|
||||
params: {
|
||||
fileIds: selectFileIdList.value.join(","),
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
loadDataList();
|
||||
emit("reload");
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/file.list.scss";
|
||||
.file-list {
|
||||
margin-top: 10px;
|
||||
.file-item {
|
||||
.op {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,213 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="top">
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="selectIdList.length == 0"
|
||||
@click="cancelShareBatch"
|
||||
>
|
||||
<span class="iconfont icon-cancel"></span>取消分享
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="file-list">
|
||||
<Table
|
||||
:columns="columns"
|
||||
:showPagination="true"
|
||||
:dataSource="tableData"
|
||||
:fetch="loadDataList"
|
||||
:options="tableOptions"
|
||||
@rowSelected="rowSelected"
|
||||
>
|
||||
<template #fileName="{ index, row }">
|
||||
<div
|
||||
class="file-item"
|
||||
@mouseenter="showOp(row)"
|
||||
@mouseleave="cancelShowOp(row)"
|
||||
>
|
||||
<template
|
||||
v-if="
|
||||
(row.fileType == 3 || row.fileType == 1) && row.status !== 0
|
||||
"
|
||||
>
|
||||
<icon :cover="row.fileCover"></icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
<icon v-if="row.folderType == 0" :fileType="row.fileType"></icon>
|
||||
<icon v-if="row.folderType == 1" :fileType="0"></icon>
|
||||
</template>
|
||||
<span
|
||||
class="file-name"
|
||||
v-if="!row.showRename"
|
||||
:title="row.fileName"
|
||||
>
|
||||
<span>{{ row.fileName }}</span>
|
||||
</span>
|
||||
<span class="op">
|
||||
<template v-if="row.showOp && row.fileId">
|
||||
<span class="iconfont icon-link" @click="copy(row)"
|
||||
>复制链接</span
|
||||
>
|
||||
<span class="iconfont icon-cancel" @click="cancelShare(row)"
|
||||
>取消分享</span
|
||||
>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #expireTime="{ index, row }">
|
||||
{{ row.validType == 3 ? "永久" : row.expireTime }}
|
||||
</template>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import useClipboard from "vue-clipboard3";
|
||||
const { toClipboard } = useClipboard();
|
||||
import { ref, reactive, getCurrentInstance, watch } from "vue";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
const { proxy } = getCurrentInstance();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const api = {
|
||||
loadDataList: "/share/loadShareList",
|
||||
cancelShare: "/share/cancelShare",
|
||||
};
|
||||
|
||||
const shareUrl = ref(document.location.origin + "/share/");
|
||||
|
||||
//列表
|
||||
const columns = [
|
||||
{
|
||||
label: "文件名",
|
||||
prop: "fileName",
|
||||
scopedSlots: "fileName",
|
||||
},
|
||||
{
|
||||
label: "分享时间",
|
||||
prop: "shareTime",
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
label: "失效时间",
|
||||
prop: "expireTime",
|
||||
scopedSlots: "expireTime",
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
label: "浏览次数",
|
||||
prop: "showCount",
|
||||
width: 200,
|
||||
},
|
||||
];
|
||||
//搜索
|
||||
const search = () => {
|
||||
showLoading.value = true;
|
||||
loadDataList();
|
||||
};
|
||||
//列表
|
||||
const tableData = ref({});
|
||||
const tableOptions = {
|
||||
extHeight: 20,
|
||||
selectType: "checkbox",
|
||||
};
|
||||
|
||||
const loadDataList = async () => {
|
||||
let params = {
|
||||
pageNo: tableData.value.pageNo,
|
||||
pageSize: tableData.value.pageSize,
|
||||
};
|
||||
if (params.category !== "all") {
|
||||
delete params.filePid;
|
||||
}
|
||||
let result = await proxy.Request({
|
||||
url: api.loadDataList,
|
||||
params,
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
tableData.value = result.data;
|
||||
};
|
||||
|
||||
//展示操作按钮
|
||||
const showOp = (row) => {
|
||||
tableData.value.list.forEach((element) => {
|
||||
element.showOp = false;
|
||||
});
|
||||
row.showOp = true;
|
||||
};
|
||||
|
||||
const cancelShowOp = (row) => {
|
||||
row.showOp = false;
|
||||
};
|
||||
|
||||
//复制链接
|
||||
const copy = async (data) => {
|
||||
await toClipboard(
|
||||
`链接:${shareUrl.value}${data.shareId} 提取码: ${data.code}`
|
||||
);
|
||||
proxy.Message.success("复制成功");
|
||||
};
|
||||
|
||||
//多选 批量选择
|
||||
const selectIdList = ref([]);
|
||||
const rowSelected = (rows) => {
|
||||
selectIdList.value = [];
|
||||
rows.forEach((item) => {
|
||||
selectIdList.value.push(item.shareId);
|
||||
});
|
||||
};
|
||||
|
||||
//取消分享
|
||||
const cancelShareIdList = ref([]);
|
||||
const cancelShareBatch = () => {
|
||||
if (selectIdList.value.length == 0) {
|
||||
return;
|
||||
}
|
||||
cancelShareIdList.value = selectIdList.value;
|
||||
cancelShareDone();
|
||||
};
|
||||
|
||||
const cancelShare = (row) => {
|
||||
cancelShareIdList.value = [row.shareId];
|
||||
cancelShareDone();
|
||||
};
|
||||
|
||||
const cancelShareDone = async () => {
|
||||
proxy.Confirm(`你确定要取消分享吗?`, async () => {
|
||||
let result = await proxy.Request({
|
||||
url: api.cancelShare,
|
||||
params: {
|
||||
shareIds: cancelShareIdList.value.join(","),
|
||||
},
|
||||
});
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
proxy.Message.success("取消分享成功");
|
||||
loadDataList();
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/assets/file.list.scss";
|
||||
.file-list {
|
||||
margin-top: 10px;
|
||||
.file-item {
|
||||
.file-name {
|
||||
span {
|
||||
&:hover {
|
||||
color: #494944;
|
||||
}
|
||||
}
|
||||
}
|
||||
.op {
|
||||
width: 170px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||