添加vue-router

This commit is contained in:
LittleBoy 2022-05-13 11:12:12 +08:00
parent 26a2be3751
commit 0f7149f1b0
36 changed files with 1191 additions and 1904 deletions

View File

@ -86,6 +86,12 @@
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.9.0</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.amqp</groupId>-->
<!-- <artifactId>spring-rabbit-test</artifactId>-->

View File

@ -0,0 +1,77 @@
package xyz.longicorn.driver.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class AuthFilter extends BasicHttpAuthenticationFilter {
//判断是否允许通过
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue);
}
//是否允许登录
@Override
protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
return super.isLoginAttempt(request, response);
}
// 创建shiro token
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
return super.createToken(request, response);
}
//isAccessAllowed为false时调用验证失败
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
this.sendChallenge(request, response);
responseError(response, "he he !");
return false;
}
//shiro验证成功调用
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
return super.onLoginSuccess(token, subject, request, response);
}
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
if (!isLoginAttempt(request, response)) {
responseError(response, "verify token failure");
return false;
}
return super.preHandle(request, response);
}
private void setCors(ServletResponse response) {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
// httpServletResponse.addHeader("Access-Control-Allow-Credentials", "true");
httpServletResponse.addHeader("Access-Control-Allow-Origin", "*");
httpServletResponse.addHeader("Access-Control-Allow-Methods", "*");
httpServletResponse.addHeader("Access-Control-Allow-Headers", "*");
}
private void responseError(ServletResponse response, Object msg) {
setCors(response);
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(401);
httpResponse.setCharacterEncoding("UTF-8");
httpResponse.setContentType("application/json;charset=UTF-8");
try {
String rj = msg instanceof String ? (String) msg : new ObjectMapper().writeValueAsString(msg);
httpResponse.getWriter().append(rj);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,17 @@
package xyz.longicorn.driver.config;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor paginationInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}

View File

@ -0,0 +1,61 @@
package xyz.longicorn.driver.config;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Autowired
private UserRealm userRealm;
//ShiroFilter过滤所有请求
@Bean(name = "shiroFilterFactoryBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("auth", new AuthFilter()); // 设置验证过滤器
shiroFilterFactoryBean.setFilters(filterMap);
//给ShiroFilter配置安全管理器
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
//配置系统公共资源
Map<String, String> map = new HashMap<String, String>();
//匿名资源 一定是在受限资源上面
map.put("/api/**", "anon");
//受限资源需要认证和授权
map.put("/**", "auth");
// 设置认证界面路径
// shiroFilterFactoryBean.setLoginUrl("/user/login");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(UserRealm userRealm) {
//设置自定义Realm
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
//关闭shiro自带的session
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
return securityManager;
}
}

View File

@ -0,0 +1,44 @@
package xyz.longicorn.driver.config;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
@Component
public class UserRealm extends AuthorizingRealm {
// 处理授权 获取凭证对该凭证赋权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取身份信息
String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
System.out.println("用户凭证:" + primaryPrincipal);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> roleSet = new HashSet<>(); // 角色列表
info.setRoles(roleSet);
Set<String> permsSet = new HashSet<>(); // 权限列表
info.setStringPermissions(permsSet);
return info;
}
// 处理认证 验证请求的凭证是否合法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
String credentials = (String) authenticationToken.getCredentials(); // 获取凭证
System.out.println("用户凭证:" + credentials);
//TODO 完成token的验证 返回数据库信息
return new SimpleAuthenticationInfo(credentials, credentials, getName());
}
}

View File

@ -0,0 +1,19 @@
package xyz.longicorn.driver.controller;
import io.swagger.annotations.Api;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xyz.longicorn.driver.dto.ApiResult;
import xyz.longicorn.driver.dto.FileItem;
@RestController
@RequestMapping("/api/file")
public class FileController {
@PostMapping("/rename")
public ApiResult rename(@Validated @RequestBody FileItem item) {
return ApiResult.success(null);
}
}

View File

@ -6,7 +6,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xyz.longicorn.driver.pojo.FileInfo;
import xyz.longicorn.driver.service.FileService;
import xyz.longicorn.driver.util.ImageUtils;
import xyz.longicorn.driver.util.FileUtils;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
@ -28,7 +28,7 @@ public class PreviewController {
if (path == null) return;
// 设置输出头
resp.addHeader("Content-Type", "image/jpeg");
ImageUtils.getPreview(path, resp.getOutputStream());
FileUtils.getImagePreview(path, resp.getOutputStream());
}
// 生成预览图
@ -64,7 +64,7 @@ public class PreviewController {
resp.getWriter().println("file not found!");
return null;
}
if (!ImageUtils.isImage(info.getType())) { //判断是否是图片
if (!FileUtils.isImage(info.getType())) { //判断是否是图片
resp.getWriter().println("file can not preview!");
return null;
}

View File

@ -0,0 +1,31 @@
package xyz.longicorn.driver.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xyz.longicorn.driver.dto.ApiResult;
import xyz.longicorn.driver.pojo.ShareInfo;
import xyz.longicorn.driver.service.ShareService;
import javax.annotation.Resource;
@RestController
@RequestMapping("/api/share")
public class ShareController {
@Resource
private ShareService shareService;
/**
* 创建分享
* @return
*/
@RequestMapping("/create")
public ApiResult create(ShareInfo info){
return ApiResult.success(shareService.create(info));
}
@RequestMapping("/info")
public ApiResult info(String id){
return ApiResult.success(shareService.getById(id));
}
public ApiResult list(ShareInfo info){
return ApiResult.success(shareService.create(info));
}
}

View File

@ -7,10 +7,12 @@ import org.springframework.web.multipart.MultipartFile;
import xyz.longicorn.driver.config.BizException;
import xyz.longicorn.driver.dto.ApiResult;
import xyz.longicorn.driver.dto.FastUploadFile;
import xyz.longicorn.driver.pojo.enums.Category;
import xyz.longicorn.driver.pojo.FileInfo;
import xyz.longicorn.driver.pojo.FolderInfo;
import xyz.longicorn.driver.service.FileService;
import xyz.longicorn.driver.service.FolderService;
import xyz.longicorn.driver.util.FileUtils;
import javax.annotation.Resource;
import java.io.File;
@ -26,15 +28,28 @@ public class UploadController {
private FileService fileService;
@Resource
private FolderService folderService;
private final static Map<String, String> FILE_MIME_TYPE = new HashMap<>();
public UploadController() {
FILE_MIME_TYPE.put("text/plain", "txt");
FILE_MIME_TYPE.put("image/png", "png");
FILE_MIME_TYPE.put("image/jpeg", "jpg");
FILE_MIME_TYPE.put("image/jpg", "jpg");
FILE_MIME_TYPE.put("image/bmp", "bmp");
FILE_MIME_TYPE.put("image/webp", "webp");
FILE_MIME_TYPE.put("image/gif", "gif");
FILE_MIME_TYPE.put("image/svg+xml", "svg");
FILE_MIME_TYPE.put("audio/mpeg", "mp3");
FILE_MIME_TYPE.put("video/mp4", "mp4");
FILE_MIME_TYPE.put("application/msword", "doc");
FILE_MIME_TYPE.put("application/pdf", "pdf");
FILE_MIME_TYPE.put("application/vnd.ms-excel", "xls");
FILE_MIME_TYPE.put("application/vnd.ms-powerpoint", "ppt");
FILE_MIME_TYPE.put("application/vnd.openxmlformats-officedocument.presentationml.presentation", "pptx");
FILE_MIME_TYPE.put("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xlsx");
FILE_MIME_TYPE.put("application/vnd.openxmlformats-officedocument.wordprocessingml.document", "docx");
}
@SneakyThrows
@ -59,7 +74,22 @@ public class UploadController {
fileInfo.setHash(md5);//
fileInfo.setName(file.getOriginalFilename());
fileInfo.setSize(file.getSize());
fileInfo.setType(getType(file.getContentType()));
String type = getType(file.getContentType());// 获取文件的扩展名类型
fileInfo.setType(type);
if (FileUtils.isImage(type)) {
fileInfo.setCategory(Category.Image);
} else if (FileUtils.isDocument(type)) {
fileInfo.setCategory(Category.Document);
} else if (FileUtils.isVideo(type)) {
fileInfo.setCategory(Category.Video);
} else if (FileUtils.isAudio(type)) {
fileInfo.setCategory(Category.Audio);
} else {
fileInfo.setCategory(Category.Other);
}
if (f != null) { // 系统已经存在了该文件
// 不保存上传文件 直接copy数据
fileInfo.setPath(f.getPath());

View File

@ -0,0 +1,8 @@
package xyz.longicorn.driver.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import xyz.longicorn.driver.pojo.ShareInfo;
@Mapper
public interface ShareInfoMapper extends BaseMapper<ShareInfo> {
}

View File

@ -0,0 +1,5 @@
package xyz.longicorn.driver.dto;
public class ShareDto {
}

View File

@ -1,13 +1,11 @@
package xyz.longicorn.driver.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.models.auth.In;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
import xyz.longicorn.driver.pojo.enums.Category;
import java.io.Serializable;
import java.util.Date;
@ -18,12 +16,16 @@ import java.util.Date;
@NoArgsConstructor
@Accessors(chain = true)
public class FileInfo implements Serializable {
private Long id;
private Integer uid;
private String name;
private String hash;
private Long folderId;
private String type;
private Category category;
private Long size;
private String path;
private Date createTime;

View File

@ -0,0 +1,14 @@
package xyz.longicorn.driver.service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import xyz.longicorn.driver.dao.ShareInfoMapper;
import xyz.longicorn.driver.pojo.ShareInfo;
@Service
public class ShareService extends ServiceImpl<ShareInfoMapper, ShareInfo> {
public ShareInfo create(ShareInfo info) {
this.save(info);
return null;
}
}

View File

@ -0,0 +1,42 @@
package xyz.longicorn.driver.util;
import lombok.SneakyThrows;
import net.coobird.thumbnailator.Thumbnails;
import java.io.File;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
public class FileUtils {
private static final List<String> ImageTypes = Arrays.asList("png", "jpg", "jpeg", "gif", "bmp", "webp");
private static final List<String> DocumentsTypes = Arrays.asList("doc", "pdf", "docx", "ppt", "pptx", "xls", "xlsx");
private static final List<String> VideoTypes = Arrays.asList("mp4", "mkv", "mpg");
private static final List<String> AudioTypes = Arrays.asList("mp3", "wav", "ogg", "flac");
/**
* 根据类型判断是否是图片
*
* @param type
* @return
*/
public static boolean isImage(String type) {
return ImageTypes.contains(type.toLowerCase());
}
public static boolean isDocument(String type) {
return DocumentsTypes.contains(type.toLowerCase());
}
public static boolean isVideo(String type) {return VideoTypes.contains(type.toLowerCase());
}
public static boolean isAudio(String type) {
return AudioTypes.contains(type.toLowerCase());
}
@SneakyThrows
public static void getImagePreview(String imagePath, OutputStream os) {
Thumbnails.of(new File(imagePath))
.size(256, 256) // 尺寸 256x256
.outputFormat("jpg") // 格式
.toOutputStream(os);
}
}

View File

@ -1,31 +0,0 @@
package xyz.longicorn.driver.util;
import lombok.SneakyThrows;
import net.coobird.thumbnailator.Thumbnails;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.List;
public class ImageUtils {
private static final List<String> imageTypes = Arrays.asList("png", "jpg", "jpeg", "gif", "bmp", "webp");
/**
* 根据类型判断是否是图片
* @param type
* @return
*/
public static boolean isImage(String type) {
return imageTypes.contains(type.toLowerCase());
}
@SneakyThrows
public static void getPreview(String imagePath, OutputStream os) {
Thumbnails.of(new File(imagePath))
.size(256, 256) // 尺寸 256x256
.outputFormat("jpg") // 格式
.toOutputStream(os);
}
}

View File

@ -70,3 +70,4 @@ mybatis-plus:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 实体类所在的包
type-aliases-package: xyz.longicorn.driver.pojo
type-enums-package: xyz.longicorn.driver.pojo.enums

View File

@ -15,17 +15,33 @@ create table folder_info
);
# 编号、用户编号、文件名、目录编号 、类型、大小、位置、创建时间、状态、hash
create table file_info(
id bigint(20) not null primary key auto_increment,
uid int(10) not null,
name varchar(50) not null,
hash varchar(32) not null comment '文件MD5特征码',
folder_id bigint(20) null default 0 comment '所在目录的编号,0表示根目录',
type varchar(5) null,
size int null default 0,
path varchar(500) not null comment '文件的物理存储位置,可以是本地路径也可以是一个网址', -- file://d:/a/b/c.txt http://
create_time datetime default current_timestamp,
update_time datetime on update current_timestamp,
status tinyint(1) default 1,
index ix_file_name(name)
);
create table file_info
(
id bigint(20) not null primary key auto_increment,
uid int(10) not null,
name varchar(50) not null,
hash varchar(32) not null comment '文件MD5特征码',
folder_id bigint(20) null default 0 comment '所在目录的编号,0表示根目录',
type varchar(5) null,
size int null default 0,
path varchar(500) not null comment '文件的物理存储位置,可以是本地路径也可以是一个网址', -- file://d:/a/b/c.txt http://
create_time datetime default current_timestamp,
update_time datetime on update current_timestamp,
status tinyint(1) default 1,
index ix_file_name (name)
);
-- 编号、分享名称、用户编号、分享的数据编号、分类类型(file|folder)、提取码、分享有效期、状态
create table share_info
(
id varchar(20) not null primary key,
title varchar(20) not null,
uid int(10) not null,
file_id bigint(20) not null,
type tinyint(1) default 1 comment '分类类型 1:文件 2文件夹',
password varchar(20) null,
live int default 0 comment '有效期',
create_time datetime default current_timestamp,
update_time datetime on update current_timestamp,
status tinyint(1) default 1,
index ix_file_name (title)
);

View File

@ -0,0 +1,12 @@
package xyz.longicorn.driver;
import org.junit.jupiter.api.Test;
import xyz.longicorn.driver.pojo.enums.Category;
public class TestEnum {
@Test
void testCategory(){
Category c = Category.Audio;
System.out.println(c);
}
}

1352
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,131 +1,14 @@
<script setup>
import { ref } from "@vue/reactivity";
import FileIcon from "./components/FileIcon.vue";
const data = ref({
display: "block",
activeIndex: 'all'
});
const tableData = [
{
id: 1,
modify_time: "2016-05-03",
name: "我的音乐",
type: "folder",
size: "-",
},
{
id: 2,
modify_time: "2016-05-03",
name: "我的文档",
type: "folder",
size: "-",
},
{
id: 3,
modify_time: "2016-05-03",
name: "我的视频",
type: "folder",
size: "-",
},
{
id: 4,
modify_time: "2016-05-03",
name: "我的图片",
type: "folder",
size: "-",
},
{
id: 5,
modify_time: "2016-05-03",
name: "APP.jpg",
type: "image",
size: "103KB",
},
];
</script>
<template>
<el-container>
<el-aside width="200px" class="main-left-nav">
<el-menu :default-active="data.activeIndex" class="main-type-filter" mode="vertical">
<el-menu-item index="all">所有文件</el-menu-item>
<el-menu-item index="rencent">最近上传</el-menu-item>
<el-menu-item index="picture">图片</el-menu-item>
<el-menu-item index="document">文档</el-menu-item>
<el-menu-item index="video">视频</el-menu-item>
</el-menu>
</el-aside>
<el-container>
<el-header></el-header>
<el-main>
<el-button @click="data.display = data.display == 'list' ? 'block' : 'list'">{{ data.display == "list" ? "block"
:
"list"
}}</el-button>
<div class="pan-file-list-wrapper">
<div class="pan-file-list-wrapper-table" v-if="data.display == 'list'">
<el-table :data="tableData" style="width: 100%">
<el-table-column type="selection" width="40px" />
<el-table-column label="文件名称">
<template #default="file">
<div>
<span>
<FileIcon :file="file.row" style="margin-right: 5px; width: 20px" />
</span>
<span>{{ file.row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="size" label="大小" width="180" />
<el-table-column prop="type" label="类型" width="180" />
<el-table-column prop="modify_time" width="180" label="修改时间" />
</el-table>
</div>
<el-row v-else style="margin-top: 20px" :gutter="20">
<el-col :xs="8" :md="6" :lg="4" v-for="(file, i) in 13" :key="i">
<div class="grid-content bg-purple" />
</el-col>
</el-row>
</div>
</el-main>
</el-container>
</el-container>
<!-- 所有子页面显示在这里 -->
<router-view />
</template>
<style lang="less">
body {
padding: 0;
margin: 0;
<script>
export default {
name: "App.vue"
}
</script>
.el-menu-item {
transition: all 0.5s;
opacity: 0.8;
<style>
&.is-active {
background-color: var(--el-color-primary-light-9);
opacity: 1;
}
}
.main-left-nav,
.main-type-filter {
height: 100vh;
}
.grid-content {
height: 80px;
border-radius: 5px;
margin: 10px 0;
}
.bg-purple {
--un-bg-opacity: 1;
background-color: rgba(192, 132, 252, var(--un-bg-opacity));
}
.pan-file-list-wrapper-table {
margin: 20px 0;
}
</style>
</style>

View File

@ -1,387 +0,0 @@
<script>
import {ArrowDown, Grid, FolderAdd,Search} from '@element-plus/icons-vue'
import FileIcon from "./components/FileIcon.vue";
import {dayjs, ElMessage, ElMessageBox} from 'element-plus'
import api from "./service/api";
import qs from "qs";
import FileUploader from "./components/file-uploader/Index.vue";
import FileBlockItem from "./components/FileBlockItem.vue";
export default {
setup(){
return {Search}
},
data() {
return {
currentActiveIndex: "all",
display: 'block',
/**
* @var {FileItem[]}
*/
fileData: [],
currentPath: '/',
fileMenuOption: [
{
name: '打开',
slug: 'open',
},
{
name: '下载',
slug: 'download',
},
{
name: '分享',
slug: 'share',
},
{
type: 'divider',
},
{
name: '复制',
slug: 'copy',
},
{
name: '移动',
slug: 'move',
},
{
name: '重命名',
slug: 'rename',
},
{
type: 'divider',
},
{
name: '删除',
slug: 'delete',
},
],
createFolder: {
visible: false,
value: '',
loading: false,
message: ''
},
search:{
value:''
}
};
},
computed: {
currentPathList() {
if (this.currentPath == '/') return []
const arr = this.currentPath.replace(/^\//, '').split('/');
const pathList = [], last = arr.pop();
let prefix = '';
arr.forEach(name => {
prefix += '/' + name;
pathList.push({name: name, path: prefix})
})
pathList.push({name: last, path: null})
return pathList;
}
},
components: {FileBlockItem, FileUploader, FileIcon, ArrowDown, Grid, FolderAdd},
mounted() {
// window.addEventListener('popstate',()=>console.log(location.href))
window.addEventListener('hashchange', this.handleHashChange) //
this.handleHashChange();
},
unmounted() {
window.removeEventListener('hashchange', this.handleHashChange); //
},
methods: {
//
async loadFileByPath(path = '/') {
this.currentPath = path //
//
try {
this.fileData = [];
const list = await api.folder.list(path);
this.fileData = list;
} catch (e) {
ElMessage.error(e.message)
}
},
//
getCurrentPath() {
const hash = location.hash;
const params = qs.parse(hash.substr(2))
return params.path || '/';
},
//
handleHashChange() {
this.loadFileByPath(this.getCurrentPath()) // 使
},
formatDate(time, format = 'MM-DD') {
return dayjs(time).format(format);
},
formatSize(a, b = 2) {
if (0 == a) return "0 B";
let c = 1024, d = b || 2, e = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
f = Math.floor(Math.log(a) / Math.log(c));
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + " " + e[f];
},
//
formatName(name) {
if (!name || name.length < 15) return name;
const extIndex = name.lastIndexOf('.'),
ext = name.substr(extIndex);
return name.substr(0, (15 - ext.length)) + "**" + ext;
},
fileMenuClick(e) {
window.alert(JSON.stringify(e));
},
createFolderClick() {
ElMessageBox.prompt(
'', '新建文件夹',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
// inputPattern:/[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/,
inputValidator(value) {
return !!value;
},
inputErrorMessage: '必须填写文件夹名称',
inputPlaceholder: '请输入文件夹名称'
})
.then(({value}) => {
//
})
.catch()
},
async handleCreateFolder() {
this.createFolder.message = ''
if (!this.createFolder.value) {
this.createFolder.message = '请填写文件夹名称'
return;
}
//
this.createFolder.loading = true;
try {
await api.folder.create(this.getCurrentPath(), this.createFolder.value);
this.createFolder.visible = false;
this.handleHashChange();
} catch (e) {
this.createFolder.message = e.message;
} finally {
this.createFolder.loading = false // loading
}
}
}
}
</script>
<template>
<div class="main-pan-app" @contextmenu="$refs.fileMenu.hideContextMenu">
<el-container>
<el-aside class="pan-left-aside">
<div class="logo-block">
<svg class="logo-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="1000" height="1000">
<path
d="M549.766 97.925c19.974 4.609 50.703 35.339 62.995 55.312 35.339-13.828 36.875 41.484 46.094 55.312 23.047-16.901 46.094-35.339 46.094-55.312 0-21.51-30.729-35.339-30.729-41.484 0-12.292 38.411-12.292 41.484-12.292 50.703 0 106.016 39.948 106.016 96.797 0 27.656-9.219 41.484-33.802 70.677h32.266c21.51 0 27.656-3.073 55.312 7.682C923.125 294.591 960 348.367 960 389.852c0 3.073 0 9.219-1.536 13.828-4.609 3.073-12.292 3.073-16.901 4.609-15.365 1.536-32.266 6.146-47.63 7.682-47.63 0-50.703 0-53.776-1.536-1.536 1.536-1.536 0-1.536 4.609 0 3.073 7.682 44.557 10.755 67.604 9.219 56.849 12.292 115.234 19.974 173.62 1.536 7.682 7.682 15.365 9.219 23.047 3.073 13.828 6.146 27.656 6.146 39.948 0 153.646-245.833 208.958-321.119 208.958h-86.042c-115.234-9.219-248.906-52.24-301.145-138.281-4.609-7.682-18.437-39.948-18.437-50.703v-36.875c3.073-16.901 7.682-33.802 21.51-50.703v-84.505l12.292-132.135c-12.292 1.536-33.802 1.536-38.411 1.536-21.51 0-38.411-3.073-61.458-6.146-7.682-1.536-18.437-3.073-24.583-6.146-4.609-1.536-3.073-12.292-3.073-13.828 0-44.557 44.557-107.552 98.333-119.844 4.609-1.536 13.828-1.536 19.974-3.073l41.484-1.536c-13.828-10.755-36.875-46.094-36.875-59.922v-29.193c13.828-58.385 62.995-82.969 106.016-82.969 1.536 0 41.484 0 41.484 12.292 0 6.146-30.729 19.974-30.729 41.484 0 1.536 6.146 32.266 16.901 32.266 3.073 0-1.536-3.073 3.073-3.073 3.073 0 32.266 10.755 36.875 10.755h12.292l3.073-3.073c-19.974-16.901-30.729-36.875-36.875-53.776 12.292 7.682 18.438 9.219 29.193 9.219 27.656 0 46.094-15.365 78.359-29.193 24.583-10.755 52.24-13.828 78.359-18.437-12.292-9.219-24.583-16.901-36.875-23.047l38.411-1.536c7.68 1.537 15.362 4.61 23.044 6.146z"
p-id="2967"></path>
</svg>
<span>牛牛的网盘</span>
</div>
<el-menu :default-active="currentActiveIndex" class="pan-left-menu">
<el-menu-item index="all">
<span>所有文件</span>
</el-menu-item>
<el-menu-item index="最近上传">
<span>所有文件</span>
</el-menu-item>
<el-menu-item index="picture">
<span>我的图片</span>
</el-menu-item>
<el-menu-item index="document">
<span>我的文档</span>
</el-menu-item>
<el-menu-item index="video">
<span>我的视频</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-container>
<el-header class="pan-header">
<div class="d-flex">
<div class="d-flex flex-1">
<div class="left-tool-bar" style="padding-top: 15px;">
<file-uploader @upload-success="handleHashChange" :current-folder="currentPath"/>
<el-button @click="createFolder.visible = true" size="default"
style="margin-left: 10px" round>
<el-icon>
<folder-add/>
</el-icon>
<span style="margin-left: 4px;">新建文件夹</span>
</el-button>
</div>
</div>
<ul class="pan-header-user-right">
<li><el-input
v-model="search.value"
class="search-box" clearable
style="border-radius:30px;"
placeholder="搜索你的文件"
:prefix-icon="Search"
/></li>
<li>帮助</li>
<li>
<el-dropdown>
<div class="el-dropdown-link">
<el-avatar class="avatar" :size="30"
src="https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png"/>
<span class="username">张三</span>
<el-icon class="el-icon--right">
<arrow-down/>
</el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>修改资料</el-dropdown-item>
<el-dropdown-item>登录日志查看</el-dropdown-item>
<el-dropdown-item>退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</li>
</ul>
</div>
</el-header>
<el-main>
<div style="line-height: 30px;" class="d-flex">
<!-- 当前路径 -->
<div style="flex:1;display: flex">
<div v-if="currentPath === '/'" style="font-weight: 700;font-size: 14px;">全部文件</div>
<div v-else>
<el-breadcrumb separator="/" style="line-height: 30px">
<el-breadcrumb-item>
<a href="#?path=/">全部文件</a>
</el-breadcrumb-item>
<el-breadcrumb-item v-for="p in currentPathList" :key="p.path">
<a v-if="p.path" :href="'#?path=' + p.path">{{ p.name }}</a>
<span v-else>{{ p.name }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</div>
<div class="d-flex">
<span
style="margin-right: 10px;color:#999;font-size: 12px;">已全部加载{{
fileData.length
}}</span>
<el-radio-group v-model="display" size="small">
<el-radio-button label="block">
<el-icon :size="16">
<svg class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<path
d="M384 128H213.333333a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333V213.333333a85.333333 85.333333 0 0 0-85.333333-85.333333zM810.666667 128h-170.666667a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333V213.333333a85.333333 85.333333 0 0 0-85.333333-85.333333zM384 554.666667H213.333333a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333v-170.666667a85.333333 85.333333 0 0 0-85.333333-85.333333zM810.666667 554.666667h-170.666667a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333v-170.666667a85.333333 85.333333 0 0 0-85.333333-85.333333z"
p-id="2910"></path>
</svg>
</el-icon>
</el-radio-button>
<el-radio-button label="list">
<el-icon :size="16">
<svg class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<path
d="M146.285714 749.714286v109.714285c0 9.728-8.557714 18.285714-18.285714 18.285715h-109.714286a18.797714 18.797714 0 0 1-18.285714-18.285715v-109.714285c0-9.728 8.557714-18.285714 18.285714-18.285715h109.714286c9.728 0 18.285714 8.557714 18.285714 18.285715z m0-219.428572v109.714286c0 9.728-8.557714 18.285714-18.285714 18.285714h-109.714286a18.797714 18.797714 0 0 1-18.285714-18.285714v-109.714286c0-9.728 8.557714-18.285714 18.285714-18.285714h109.714286c9.728 0 18.285714 8.557714 18.285714 18.285714z m0-219.428571v109.714286c0 9.728-8.557714 18.285714-18.285714 18.285714h-109.714286a18.797714 18.797714 0 0 1-18.285714-18.285714v-109.714286c0-9.728 8.557714-18.285714 18.285714-18.285714h109.714286c9.728 0 18.285714 8.557714 18.285714 18.285714z m877.714286 438.857143v109.714285c0 9.728-8.557714 18.285714-18.285714 18.285715h-768a18.797714 18.797714 0 0 1-18.285715-18.285715v-109.714285c0-9.728 8.557714-18.285714 18.285715-18.285715h768c9.728 0 18.285714 8.557714 18.285714 18.285715zM146.285714 91.428571v109.714286c0 9.728-8.557714 18.285714-18.285714 18.285714h-109.714286a18.797714 18.797714 0 0 1-18.285714-18.285714v-109.714286c0-9.728 8.557714-18.285714 18.285714-18.285714h109.714286c9.728 0 18.285714 8.557714 18.285714 18.285714z m877.714286 438.857143v109.714286c0 9.728-8.557714 18.285714-18.285714 18.285714h-768a18.797714 18.797714 0 0 1-18.285715-18.285714v-109.714286c0-9.728 8.557714-18.285714 18.285715-18.285714h768c9.728 0 18.285714 8.557714 18.285714 18.285714z m0-219.428571v109.714286c0 9.728-8.557714 18.285714-18.285714 18.285714h-768a18.797714 18.797714 0 0 1-18.285715-18.285714v-109.714286c0-9.728 8.557714-18.285714 18.285715-18.285714h768c9.728 0 18.285714 8.557714 18.285714 18.285714z m0-219.428572v109.714286c0 9.728-8.557714 18.285714-18.285714 18.285714h-768a18.797714 18.797714 0 0 1-18.285715-18.285714v-109.714286c0-9.728 8.557714-18.285714 18.285715-18.285714h768c9.728 0 18.285714 8.557714 18.285714 18.285714z"></path>
</svg>
</el-icon>
</el-radio-button>
</el-radio-group>
</div>
</div>
<div class="display-type-list" v-if="display == 'list'">
<el-table :data="fileData" style="width: 100%">
<el-table-column type="selection"/>
<el-table-column label="名称">
<!-- 自定义单元格内容 -->
<template #default="file">
<div class="list-file-info">
<FileIcon class="list-file-icon" style="width: 40px;" :file="file.row"
:ext="file.row.type"/>
<span class="list-file-name">{{ file.row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" width="200"/>
<el-table-column prop="size" label="大小" width="200"/>
<el-table-column label="创建时间" width="200">
<template #default="file">
<span class="list-file-time">{{ formatDate(file.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
</div>
<div class="display-type-block" v-else>
<el-row :gutter="20">
<el-col :xs="12" :sm="6" :md="4" :lg="3" :xl="2" v-for="(file, index) in fileData"
:key="index">
<file-block-item :file="file" />
</el-col>
</el-row>
</div>
</el-main>
</el-container>
</el-container>
<!-- <context-menu class="file-cxt-menu" element-id="myMenu1" ref="fileMenu" :options="fileMenuOption"-->
<!-- @option-clicked="fileMenuClick"/>-->
<!-- 新建目录 -->
<el-dialog v-model="createFolder.visible" title="新建文件夹" width="500px">
<div>
<el-input placeholder="请输入文件夹名称" v-model="createFolder.value"/>
<div style="color: red;height: 30px;line-height: 30px;">{{ createFolder.message }}</div>
</div>
<template #footer>
<!-- 将此模板的内容指定插入到footer的位置-->
<div class="dialog-footer">
<el-button @click="createFolder.visible = false">取消</el-button>
<el-button type="primary" :loading="createFolder.loading" @click="handleCreateFolder">确定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<style lang="less">
//
.file-block-item {
margin: 10px 0;
text-align: center;
padding: 10px;
cursor: pointer;
border-radius: 5px;
&:hover {
background-color: var(--el-color-primary-light-8);
}
.file-image {
height: 130px;
display: flex;
align-items: center;
> span {
margin: auto;
}
}
.file-name {
font-size: 16px;
}
.file-info {
margin-top: 3px;
font-size: 12px;
color: #999;
}
}
</style>

View File

@ -37,6 +37,15 @@
border-radius: 30px;
}
}
.share-create-dialog{
.item{
margin: 20px 0;
}
.title{
line-height: 32px;
margin-right: 20px;
}
}
.logo-icon{
width: 32px;
height: 32px;

View File

@ -1,6 +1,5 @@
<template>
<div class="file-block-item" @click="showFile(file,$event)"
@contextmenu.prevent.stop="$parent.$refs.fileMenu.showMenu($event,file)">
<div class="file-block-item" @click="showFile(file,$event)">
<div class="file-image">
<FileIcon :file="file" ref="fileItemIcon" style="width:90%"/>
</div>

View File

@ -1,6 +1,6 @@
<template>
<el-dropdown>
<el-button type="primary" round>
<el-button type="primary">
<label for="file_for_one">
<el-icon class="el-icon--right">
<Upload/>

View File

@ -8,9 +8,11 @@ import './assets/main.less'
import ContextMenu from 'vue-simple-context-menu'
import 'vue-simple-context-menu/dist/vue-simple-context-menu.css';
// 导入入口组件
import App from "./Main.vue";
import App from "./App.vue";
import index from './router'
const app = createApp(App)
app.component('ContextMenu', ContextMenu);
app.use(ElementPlus)
.use(index) // 使用路由实例
.mount("#app");

362
web/src/pages/All.vue Normal file
View File

@ -0,0 +1,362 @@
<template>
<div style="line-height: 30px;" class="d-flex">
<!-- 当前路径 -->
<div style="flex:1;display: flex">
<div v-if="currentPath === '/'" style="font-weight: 700;font-size: 14px;">全部文件</div>
<div v-else>
<el-breadcrumb separator="/" style="line-height: 30px">
<el-breadcrumb-item>
<a href="#?path=/">全部文件</a>
</el-breadcrumb-item>
<el-breadcrumb-item v-for="p in currentPathList" :key="p.path">
<a v-if="p.path" :href="'#?path=' + p.path">{{ p.name }}</a>
<span v-else>{{ p.name }}</span>
</el-breadcrumb-item>
</el-breadcrumb>
</div>
</div>
<div class="d-flex">
<span
style="margin-right: 10px;color:#999;font-size: 12px;">已全部加载{{
fileData.length
}}</span>
<el-radio-group v-model="display" size="small">
<el-radio-button label="block">
<el-icon :size="16">
<svg class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<path
d="M384 128H213.333333a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333V213.333333a85.333333 85.333333 0 0 0-85.333333-85.333333zM810.666667 128h-170.666667a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333V213.333333a85.333333 85.333333 0 0 0-85.333333-85.333333zM384 554.666667H213.333333a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333v-170.666667a85.333333 85.333333 0 0 0-85.333333-85.333333zM810.666667 554.666667h-170.666667a85.333333 85.333333 0 0 0-85.333333 85.333333v170.666667a85.333333 85.333333 0 0 0 85.333333 85.333333h170.666667a85.333333 85.333333 0 0 0 85.333333-85.333333v-170.666667a85.333333 85.333333 0 0 0-85.333333-85.333333z"
p-id="2910"></path>
</svg>
</el-icon>
</el-radio-button>
<el-radio-button label="list">
<el-icon :size="16">
<svg class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<path
d="M146.285714 749.714286v109.714285c0 9.728-8.557714 18.285714-18.285714 18.285715h-109.714286a18.797714 18.797714 0 0 1-18.285714-18.285715v-109.714285c0-9.728 8.557714-18.285714 18.285714-18.285715h109.714286c9.728 0 18.285714 8.557714 18.285714 18.285715z m0-219.428572v109.714286c0 9.728-8.557714 18.285714-18.285714 18.285714h-109.714286a18.797714 18.797714 0 0 1-18.285714-18.285714v-109.714286c0-9.728 8.557714-18.285714 18.285714-18.285714h109.714286c9.728 0 18.285714 8.557714 18.285714 18.285714z m0-219.428571v109.714286c0 9.728-8.557714 18.285714-18.285714 18.285714h-109.714286a18.797714 18.797714 0 0 1-18.285714-18.285714v-109.714286c0-9.728 8.557714-18.285714 18.285714-18.285714h109.714286c9.728 0 18.285714 8.557714 18.285714 18.285714z m877.714286 438.857143v109.714285c0 9.728-8.557714 18.285714-18.285714 18.285715h-768a18.797714 18.797714 0 0 1-18.285715-18.285715v-109.714285c0-9.728 8.557714-18.285714 18.285715-18.285715h768c9.728 0 18.285714 8.557714 18.285714 18.285715zM146.285714 91.428571v109.714286c0 9.728-8.557714 18.285714-18.285714 18.285714h-109.714286a18.797714 18.797714 0 0 1-18.285714-18.285714v-109.714286c0-9.728 8.557714-18.285714 18.285714-18.285714h109.714286c9.728 0 18.285714 8.557714 18.285714 18.285714z m877.714286 438.857143v109.714286c0 9.728-8.557714 18.285714-18.285714 18.285714h-768a18.797714 18.797714 0 0 1-18.285715-18.285714v-109.714286c0-9.728 8.557714-18.285714 18.285715-18.285714h768c9.728 0 18.285714 8.557714 18.285714 18.285714z m0-219.428571v109.714286c0 9.728-8.557714 18.285714-18.285714 18.285714h-768a18.797714 18.797714 0 0 1-18.285715-18.285714v-109.714286c0-9.728 8.557714-18.285714 18.285715-18.285714h768c9.728 0 18.285714 8.557714 18.285714 18.285714z m0-219.428572v109.714286c0 9.728-8.557714 18.285714-18.285714 18.285714h-768a18.797714 18.797714 0 0 1-18.285715-18.285714v-109.714286c0-9.728 8.557714-18.285714 18.285715-18.285714h768c9.728 0 18.285714 8.557714 18.285714 18.285714z"></path>
</svg>
</el-icon>
</el-radio-button>
</el-radio-group>
</div>
</div>
<div class="display-type-list" v-if="display == 'list'">
<el-table :data="fileData" style="width: 100%">
<el-table-column type="selection"/>
<el-table-column label="名称">
<!-- 自定义单元格内容 -->
<template #default="file">
<div class="list-file-info">
<FileIcon class="list-file-icon" style="width: 40px;" :file="file.row"
:ext="file.row.type"/>
<span class="list-file-name">{{ file.row.name }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" width="200"/>
<el-table-column prop="size" label="大小" width="200"/>
<el-table-column label="创建时间" width="200">
<template #default="file">
<span class="list-file-time">{{ formatDate(file.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
</div>
<div class="display-type-block" v-else>
<el-row :gutter="20">
<el-col :xs="12" :sm="6" :md="4" :lg="3" :xl="2" v-for="(file, index) in fileData"
:key="index">
<file-block-item :file="file"
@contextmenu.prevent.stop="$refs.fileMenu.showMenu($event,file)"/>
</el-col>
</el-row>
</div>
<!-- 右键菜单 -->
<context-menu class="file-cxt-menu" element-id="myMenu1" ref="fileMenu" :options="fileMenuOption"
@option-clicked="fileMenuClick"/>
<!-- 新建目录 -->
<el-dialog v-model="createFolder.visible" title="新建文件夹" width="500px">
<div>
<el-input placeholder="请输入文件夹名称" v-model="createFolder.value"/>
<div style="color: red;height: 30px;line-height: 30px;">{{ createFolder.message }}</div>
</div>
<template #footer>
<!-- 将此模板的内容指定插入到footer的位置-->
<div class="dialog-footer">
<el-button @click="createFolder.visible = false">取消</el-button>
<el-button type="primary" :loading="createFolder.loading" @click="handleCreateFolder">确定</el-button>
</div>
</template>
</el-dialog>
<!-- 分享 -->
<el-dialog v-model="share.visible" title="分享文件" width="600px">
<div class="share-create-dialog">
<h2>{{ share.current?.name }}</h2>
<div class="live d-flex item">
<div class="title">有效期:</div>
<div class="content">
<el-radio-group v-model="share.live">
<el-radio-button label="1">1</el-radio-button>
<el-radio-button label="7">7</el-radio-button>
<el-radio-button label="30">30</el-radio-button>
</el-radio-group>
</div>
</div>
<div class="password d-flex item">
<div class="title">提取码:</div>
<div class="content">
<el-radio-group v-model="share.password">
<el-radio-button label="yes">需要</el-radio-button>
<el-radio-button label="no">不需要</el-radio-button>
</el-radio-group>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<span style="color: #999;font-size: 12px;float: left;">配合净网行动, 网盘严厉打击色情低俗等不良信息的传播行为</span>
<el-button type="primary" :loading="share.loading" @click="handleCreateFolder">创建分享</el-button>
</div>
</template>
</el-dialog>
</template>
<script>
import {ArrowDown, Grid, FolderAdd, Search} from '@element-plus/icons-vue'
import FileIcon from "../components/FileIcon.vue";
import {dayjs, ElMessage, ElMessageBox} from 'element-plus'
import api from "../service/api";
import qs from "qs";
import FileBlockItem from "../components/FileBlockItem.vue";
export default {
name: "All",
data() {
return {
display: 'block',
/**
* @var {FileItem[]}
*/
fileData: [],
currentPath: '/',
fileMenuOption: [
{
name: '打开',
slug: 'open',
},
{
name: '下载',
slug: 'download',
},
{
name: '分享',
slug: 'share',
},
{
type: 'divider',
},
{
name: '复制',
slug: 'copy',
},
{
name: '移动',
slug: 'move',
},
{
name: '重命名',
slug: 'rename',
},
{
type: 'divider',
},
{
name: '删除',
slug: 'delete',
},
],
createFolder: {
visible: false,
value: '',
loading: false,
message: ''
},
share: {
/**
* @var {FileItem}
*/
current: null,
visible: false,
loading: false,
/**
* @var {ShareInfo}
*/
info: null,
live: 1,
password: 'yes'
}
}
},
computed: {
currentPathList() {
if (this.currentPath == '/') return []
const arr = this.currentPath.replace(/^\//, '').split('/');
const pathList = [], last = arr.pop();
let prefix = '';
arr.forEach(name => {
prefix += '/' + name;
pathList.push({name: name, path: prefix})
})
pathList.push({name: last, path: null})
return pathList;
}
},
components: {FileBlockItem, FileIcon, ArrowDown, Grid, FolderAdd},
mounted() {
console.log('记载所有的文件')
window.addEventListener('popstate',()=>console.log(location.href))
window.addEventListener('hashchange', this.handleHashChange) //
this.handleHashChange();
},
unmounted() {
window.removeEventListener('hashchange', this.handleHashChange); //
},
methods: {
//
async loadFileByPath(path = '/') {
this.currentPath = path //
//
try {
this.fileData = [];
const list = await api.folder.list(path);
this.fileData = list;
} catch (e) {
ElMessage.error(e.message)
}
},
//
getCurrentPath() {
const hash = location.hash;
const params = qs.parse(hash.substr(2))
return params.path || '/';
},
//
handleHashChange() {
this.loadFileByPath(this.getCurrentPath()) // 使
},
formatDate(time, format = 'MM-DD') {
return dayjs(time).format(format);
},
formatSize(a, b = 2) {
if (0 == a) return "0 B";
let c = 1024, d = b || 2, e = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"],
f = Math.floor(Math.log(a) / Math.log(c));
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + " " + e[f];
},
//
formatName(name) {
if (!name || name.length < 15) return name;
const extIndex = name.lastIndexOf('.'),
ext = name.substr(extIndex);
return name.substr(0, (15 - ext.length)) + "**" + ext;
},
fileMenuClick(data) {
const {item, option} = data;
console.log(data)
if (option.slug == 'share') {
//
this.share.current = item
this.share.visible = true;
} else if (option.slug == 'rename') {
this.renameFile(item);
} else if (option.slug == 'delete') {
this.deleteFile(item);
}
},
/**
*
* @param {FileItem} file
*/
renameFile(file) {
ElMessageBox.prompt('', '请输入新的文件名', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputValue: file.name,
closeOnClickModal: false
}).then(({value}) => {
api.file.rename({
...file,
name: value
}).then(() => {
file.name = value;
}).catch();
}).catch();
},
/**
*
* @param {FileItem} file
*/
deleteFile(file) {
ElMessageBox.confirm(
'确定要删除选中文件吗?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消'
}
)
.then(() => {
api.file.delete(file).then(() => this.handleHashChange()).catch();
}).catch();
},
createFolderClick() {
ElMessageBox.prompt(
'', '新建文件夹',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
closeOnClickModal: false,
// inputPattern:/[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/,
inputValidator(value) {
return !!value;
},
inputErrorMessage: '必须填写文件夹名称',
inputPlaceholder: '请输入文件夹名称'
})
.then(({value}) => {
//
})
.catch()
},
async handleCreateFolder() {
this.createFolder.message = ''
if (!this.createFolder.value) {
this.createFolder.message = '请填写文件夹名称'
return;
}
//
this.createFolder.loading = true;
try {
await api.folder.create(this.getCurrentPath(), this.createFolder.value);
this.createFolder.visible = false;
this.handleHashChange();
} catch (e) {
this.createFolder.message = e.message;
} finally {
this.createFolder.loading = false // loading
}
}
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,13 @@
<template>
<h1>文档</h1>
</template>
<script>
export default {
name: "Documents"
}
</script>
<style scoped>
</style>

81
web/src/pages/Login.vue Normal file
View File

@ -0,0 +1,81 @@
<style scoped>
#page-login {
width: 500px;
margin: 200px auto 0;
}
</style>
<template>
<div id="page-login">
<el-form
ref="form"
:model="loginForm"
:rules="rules"
status-icon
label-width="120px"
class="demo-ruleForm"
>
<el-form-item label="" prop="username">
<el-input v-model="loginForm.username" placeholder="用户名" autocomplete="off"/>
</el-form-item>
<el-form-item label="" prop="password">
<el-input v-model="loginForm.password" placeholder="登录密码" type="password" autocomplete="off"/>
</el-form-item>
<el-form-item>
<el-button style="width: 100%" type="primary" @click="submitForm">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
import {defineComponent, reactive, ref} from 'vue'
import {ElMessage, ElMessageBox} from "element-plus";
import {useRouter} from 'vue-router'
export default defineComponent({
name: 'Login',
method:{
gotoMain(){
this.$router.push('/');
}
},
setup() {
/**
*
* @type {Ref<FormInstance>}
*/
const form = ref(null);
// 使reactive
const loginForm = reactive({
password: '',
username: ''
})
const checkPassword = (rule, value, callback) => {
if (!value) callback(Error('请填写密码')) //
else callback() //
}
const rules = reactive({
username: [
{required: true, message: '请填写用户名称', trigger: 'blur'}
],
password: [
{validator: checkPassword, trigger: 'blur'}
]
})
const router = useRouter(); //
const submitForm = () => {
form.value.validate((isValid) => {
if (isValid) {
ElMessage.success('登录成功')
router.replace('/')
} else {
ElMessage.info('验证不通过')
}
})
};
return {
loginForm, submitForm, rules, form
}
}
})
</script>

157
web/src/pages/Main.vue Normal file
View File

@ -0,0 +1,157 @@
<script>
import {ArrowDown, Grid, FolderAdd, Search} from '@element-plus/icons-vue'
import FileUploader from "../components/file-uploader/Index.vue";
import {ElMessage} from "element-plus";
export default {
setup() {
return {Search}
},
components: {ArrowDown, Grid, FolderAdd, FileUploader},
data() {
return {
currentActiveIndex: "/",
currentPath: '/',
search: {
value: ''
},
};
},
mounted() {
this.currentActiveIndex = this.$route.path
},
methods: {
//
handleHashChange() {
ElMessage.info({
title: '成功 加载页面'
})
},
}
}
</script>
<template>
<div class="main-pan-app">
<el-container>
<el-aside class="pan-left-aside">
<div class="logo-block">
<svg class="logo-icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="1000" height="1000">
<path
d="M549.766 97.925c19.974 4.609 50.703 35.339 62.995 55.312 35.339-13.828 36.875 41.484 46.094 55.312 23.047-16.901 46.094-35.339 46.094-55.312 0-21.51-30.729-35.339-30.729-41.484 0-12.292 38.411-12.292 41.484-12.292 50.703 0 106.016 39.948 106.016 96.797 0 27.656-9.219 41.484-33.802 70.677h32.266c21.51 0 27.656-3.073 55.312 7.682C923.125 294.591 960 348.367 960 389.852c0 3.073 0 9.219-1.536 13.828-4.609 3.073-12.292 3.073-16.901 4.609-15.365 1.536-32.266 6.146-47.63 7.682-47.63 0-50.703 0-53.776-1.536-1.536 1.536-1.536 0-1.536 4.609 0 3.073 7.682 44.557 10.755 67.604 9.219 56.849 12.292 115.234 19.974 173.62 1.536 7.682 7.682 15.365 9.219 23.047 3.073 13.828 6.146 27.656 6.146 39.948 0 153.646-245.833 208.958-321.119 208.958h-86.042c-115.234-9.219-248.906-52.24-301.145-138.281-4.609-7.682-18.437-39.948-18.437-50.703v-36.875c3.073-16.901 7.682-33.802 21.51-50.703v-84.505l12.292-132.135c-12.292 1.536-33.802 1.536-38.411 1.536-21.51 0-38.411-3.073-61.458-6.146-7.682-1.536-18.437-3.073-24.583-6.146-4.609-1.536-3.073-12.292-3.073-13.828 0-44.557 44.557-107.552 98.333-119.844 4.609-1.536 13.828-1.536 19.974-3.073l41.484-1.536c-13.828-10.755-36.875-46.094-36.875-59.922v-29.193c13.828-58.385 62.995-82.969 106.016-82.969 1.536 0 41.484 0 41.484 12.292 0 6.146-30.729 19.974-30.729 41.484 0 1.536 6.146 32.266 16.901 32.266 3.073 0-1.536-3.073 3.073-3.073 3.073 0 32.266 10.755 36.875 10.755h12.292l3.073-3.073c-19.974-16.901-30.729-36.875-36.875-53.776 12.292 7.682 18.438 9.219 29.193 9.219 27.656 0 46.094-15.365 78.359-29.193 24.583-10.755 52.24-13.828 78.359-18.437-12.292-9.219-24.583-16.901-36.875-23.047l38.411-1.536c7.68 1.537 15.362 4.61 23.044 6.146z"
p-id="2967"></path>
</svg>
<span>牛牛的网盘</span>
</div>
<el-menu :default-active="currentActiveIndex" class="pan-left-menu" :router="true">
<el-menu-item index="/">
<span>所有文件</span>
</el-menu-item>
<el-menu-item index="/histories">
<span>最近上传</span>
</el-menu-item>
<el-menu-item index="/pictures">
<span>我的图片</span>
</el-menu-item>
<el-menu-item index="/documents">
<span>我的文档</span>
</el-menu-item>
<el-menu-item index="/videos">
<span>我的视频</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-container>
<el-header class="pan-header">
<div class="d-flex">
<div class="d-flex flex-1">
<div class="left-tool-bar" style="padding-top: 15px;">
<file-uploader @upload-success="handleHashChange" :current-folder="currentPath"/>
<el-button @click="createFolder.visible = true" size="default"
style="margin-left: 10px">
<el-icon>
<folder-add/>
</el-icon>
<span style="margin-left: 4px;">新建文件夹</span>
</el-button>
</div>
</div>
<ul class="pan-header-user-right">
<li>
<el-input
v-model="search.value"
class="search-box" clearable
style="border-radius:30px;"
placeholder="搜索你的文件"
:prefix-icon="Search"
/>
</li>
<li>帮助</li>
<li>
<el-dropdown>
<div class="el-dropdown-link">
<el-avatar class="avatar" :size="30"
src="https://cube.elemecdn.com/9/c2/f0ee8a3c7c9638a54940382568c9dpng.png"/>
<span class="username">张三</span>
<el-icon class="el-icon--right">
<arrow-down/>
</el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>修改资料</el-dropdown-item>
<el-dropdown-item>登录日志查看</el-dropdown-item>
<el-dropdown-item>退出</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</li>
</ul>
</div>
</el-header>
<el-main>
<!-- 子路由显示的地方 -->
<router-view/>
</el-main>
</el-container>
</el-container>
</div>
</template>
<style lang="less">
//
.file-block-item {
margin: 10px 0;
text-align: center;
padding: 10px;
cursor: pointer;
border-radius: 5px;
&:hover {
background-color: var(--el-color-primary-light-8);
}
.file-image {
height: 130px;
display: flex;
align-items: center;
> span {
margin: auto;
}
}
.file-name {
font-size: 16px;
}
.file-info {
margin-top: 3px;
font-size: 12px;
color: #999;
}
}
</style>

View File

@ -0,0 +1,13 @@
<template>
<h1>图片</h1>
</template>
<script>
export default {
name: "Pictures"
}
</script>
<style scoped>
</style>

13
web/src/pages/Reg.vue Normal file
View File

@ -0,0 +1,13 @@
<template>
<h1>Reg</h1>
</template>
<script>
export default {
name: "Reg.vue"
}
</script>
<style scoped>
</style>

13
web/src/pages/Videos.vue Normal file
View File

@ -0,0 +1,13 @@
<template>
<h1>视频</h1>
</template>
<script>
export default {
name: "Videos"
}
</script>
<style scoped>
</style>

13
web/src/router/index.js Normal file
View File

@ -0,0 +1,13 @@
import {createRouter, createWebHistory} from 'vue-router'
import routes from "./routes";
const router = createRouter({
history: createWebHistory(),
routes
});
router.beforeEach((to,from,next)=>{
console.log(from.path ,'==>',to.path)
next()
})
export default router;

52
web/src/router/routes.js Normal file
View File

@ -0,0 +1,52 @@
import Main from '../pages/Main.vue'
/**
*
* @type {RouteRecordRaw[]}
*/
const routes = [
{
name: 'Main',
path: '/',
component: Main, // 需要在父页面添加 <router-view /> 来显示子路由页面
// 配置子路由
children: [
{
name: 'All',
path: '/',
component: () => import('../pages/All.vue'),
},
{
name: 'Histories',
path: '/histories',
component: () => import('../pages/All.vue'),
},
{
name: 'Pictures',
path: '/pictures',
component: () => import('../pages/Pictures.vue'),
},
{
name: 'Documents',
path: '/documents',
component: () => import('../pages/Documents.vue'),
},
{
name: 'Videos',
path: '/videos',
component: () => import('../pages/Videos.vue'),
}
]
},
{
name: 'Reg',
path: '/reg',
component: () => import('../pages/Reg.vue')
},
{
name: 'Login',
path: '/login',
component: () => import('../pages/Login.vue')
}
];
export default routes

View File

@ -83,6 +83,12 @@ export default {
fastUpload(params) {
return request('/api/fast-upload', 'POST', params);
},
delete(file) {
return request('/api/file/delete', 'POST', file);
},
rename(file) {
return request('/api/file/rename', 'POST', file);
},
upload(parent, file, onProcess = null) {
// 上传文件到某个目录
const postData = new FormData(); // 将数据封装成form表单
@ -119,5 +125,14 @@ export default {
})
// return request('/api/upload', 'FILE', postData, onProcess)
}
},
share: {
/**
*
* @param {ShareInfo} info
*/
create(info) {
return request('/api/share/create', 'POST', info)
}
}
}

View File

@ -20,3 +20,18 @@ declare type FileIconInstance = {
*/
showPreview: Function
}
/**
*
*/
declare type ShareInfo = {
id?: string,
title: string,
uid: number,
fileId: number,
type: 1 | 2,
password: string,
live: number,
createTime?: string,
updateTime?: string,
status: number
}