├── .env.development ├── .env.production ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── index.html └── logo.png └── src ├── App.vue ├── apis ├── file.js ├── request.js ├── resource.js └── system.js ├── assets ├── file_icons.png ├── image-icon.png ├── text-icon.png ├── user.jpeg └── video-icon.png ├── components ├── FileUpload.vue ├── FolderTreeDialog.vue └── uploader │ ├── btn.vue │ ├── common │ ├── file-events.js │ ├── mixins.js │ └── utils.js │ ├── drop.vue │ ├── file.vue │ ├── files.vue │ ├── list.vue │ ├── unsupport.vue │ └── uploader.vue ├── main.js ├── router └── index.js ├── store └── index.js ├── styles ├── index.scss └── layout.scss ├── utils └── index.js └── views ├── 404.vue ├── file └── index.vue ├── layout ├── Aside.vue ├── Header.vue └── Layout.vue ├── system └── index.vue └── video └── index.vue /.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_API_URI=http://localhost:8989 -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_API_URI=/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pnd-web 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | npm run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | npm run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | 31 | 后端地址:[https://github.com/BitInit/pnd](https://github.com/BitInit/pnd) -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pnd-fe", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "axios": "^0.19.0", 12 | "core-js": "^3.4.4", 13 | "dplayer": "^1.25.0", 14 | "element-ui": "^2.13.0", 15 | "simple-uploader.js": "^0.5.4", 16 | "spark-md5": "^3.0.0", 17 | "vue": "^2.6.10", 18 | "vue-router": "^3.1.3", 19 | "vuex": "^3.1.2" 20 | }, 21 | "devDependencies": { 22 | "@vue/cli-plugin-babel": "^4.1.0", 23 | "@vue/cli-plugin-eslint": "^4.1.0", 24 | "@vue/cli-service": "^4.1.0", 25 | "babel-eslint": "^10.0.3", 26 | "css-loader": "^3.4.1", 27 | "eslint": "^5.16.0", 28 | "eslint-plugin-vue": "^5.0.0", 29 | "node-sass": "^4.13.0", 30 | "sass-loader": "^8.0.0", 31 | "vue-template-compiler": "^2.6.10" 32 | }, 33 | "eslintConfig": { 34 | "root": true, 35 | "env": { 36 | "node": true 37 | }, 38 | "extends": [ 39 | "plugin:vue/essential", 40 | "eslint:recommended" 41 | ], 42 | "rules": {}, 43 | "parserOptions": { 44 | "parser": "babel-eslint" 45 | } 46 | }, 47 | "browserslist": [ 48 | "> 1%", 49 | "last 2 versions" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 网盘 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitInit/pnd-web/807c2216b0c5eaab9bb224f6973b4f30c3420241/public/logo.png -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 28 | -------------------------------------------------------------------------------- /src/apis/file.js: -------------------------------------------------------------------------------- 1 | import request from './request' 2 | 3 | /** 4 | * 根据parentId获取所有的文件 5 | * @param {Number} parentId 6 | */ 7 | export const getFileList = (parentId) => { 8 | return request({ 9 | url: `/v1/file/parent/${parentId}` 10 | }) 11 | } 12 | 13 | /** 14 | * 根据fileId获取文件 15 | * @param {Number} fileId 文件id 16 | */ 17 | export const getFile = (fileId) => { 18 | return request({ 19 | url: `/v1/file/${fileId}` 20 | }) 21 | } 22 | 23 | /** 24 | * 下载文件URL 25 | * @param {Number} fileId fileId 26 | */ 27 | export const downloadFileUrl = (fileId) => { 28 | if (process.env.NODE_ENV === 'production') { 29 | return window.location.origin + `/v1/file/${fileId}/download` 30 | } else { 31 | return process.env.VUE_APP_API_URI + `/v1/file/${fileId}/download` 32 | } 33 | } 34 | 35 | /** 36 | * 创建新的文件 37 | * @param {Number}} parentId 父文件夹id 38 | * @param {String} fileName 文件名 39 | * @param {String} type 文件类型:folder/txt/pdf... 40 | */ 41 | export const createFile = (parentId, fileName, type) => { 42 | return request({ 43 | url: `/v1/file`, 44 | method: 'post', 45 | data: { 46 | parentId, fileName, type 47 | } 48 | }) 49 | } 50 | 51 | /** 52 | * 文件重命名 53 | * @param {Number} fileId 文件id 54 | * @param {String} fileName 文件名 55 | */ 56 | export const renameFile = (fileId, fileName) => { 57 | return request({ 58 | url: `/v1/file/${fileId}/rename`, 59 | method: 'put', 60 | data: { 61 | fileName 62 | } 63 | }) 64 | } 65 | 66 | /** 67 | * 根据文件id删除文件 68 | * @param {Array} fileIds 文件ids 69 | */ 70 | export const deleteFiles = (fileIds) => { 71 | return request({ 72 | url: `/v1/file`, 73 | method: 'delete', 74 | data: fileIds 75 | }) 76 | } 77 | 78 | /** 79 | * 移动或复制文件 80 | * @param {Array} fileIds 源文件ids 81 | * @param {Array} targetIds 目标文件ids 82 | * @param {String} type 类型 83 | */ 84 | export const moveOrCopyFiles = (fileIds, targetIds, type) => { 85 | return request({ 86 | url: '/v1/file', 87 | method: 'put', 88 | data: { 89 | fileIds, targetIds, type 90 | } 91 | }) 92 | } -------------------------------------------------------------------------------- /src/apis/request.js: -------------------------------------------------------------------------------- 1 | import Axios from 'axios' 2 | import { Message } from 'element-ui' 3 | import router from '@/router' 4 | 5 | let req = Axios.create({ 6 | baseURL: process.env.VUE_APP_API_URI, 7 | timeout: 15000 8 | }) 9 | 10 | const errorHandle = (status) => { 11 | if (status === 404){ 12 | router.push('/404') 13 | } 14 | } 15 | 16 | req.interceptors.response.use(response => { 17 | return response.data 18 | }, error => { 19 | const { response } = error 20 | if (response) { 21 | errorHandle(response.status) 22 | return Promise.reject(response) 23 | } else { 24 | // 处理断网 25 | if (!window.navigator.onLine){ 26 | alert("断网了") 27 | } else { 28 | Message({ 29 | message: error, 30 | type: 'error' 31 | }) 32 | } 33 | } 34 | return Promise.reject(error) 35 | }) 36 | 37 | export default req -------------------------------------------------------------------------------- /src/apis/resource.js: -------------------------------------------------------------------------------- 1 | import request from './request' 2 | 3 | /** 4 | * 上传文件地址 5 | */ 6 | export const resourceUploadUrl = () => { 7 | if (process.env.NODE_ENV === 'production') { 8 | return window.location.origin + '/v1/resource/chunk' 9 | } 10 | return process.env.VUE_APP_API_URI + `/v1/resource/chunk` 11 | } 12 | 13 | /** 14 | * 合并上传的资源 15 | * @param {Object} resource resource 16 | */ 17 | export const mergeResource = (resource) => { 18 | return request({ 19 | url: '/v1/resource/merge', 20 | method: 'post', 21 | data: resource 22 | }) 23 | } -------------------------------------------------------------------------------- /src/apis/system.js: -------------------------------------------------------------------------------- 1 | import request from './request' 2 | 3 | /** 4 | * 获取系统信息,例如:系统容量,文件数等 5 | */ 6 | export const systemInfo = () => { 7 | return request({ 8 | url: `/v1/system` 9 | }) 10 | } -------------------------------------------------------------------------------- /src/assets/file_icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitInit/pnd-web/807c2216b0c5eaab9bb224f6973b4f30c3420241/src/assets/file_icons.png -------------------------------------------------------------------------------- /src/assets/image-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitInit/pnd-web/807c2216b0c5eaab9bb224f6973b4f30c3420241/src/assets/image-icon.png -------------------------------------------------------------------------------- /src/assets/text-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitInit/pnd-web/807c2216b0c5eaab9bb224f6973b4f30c3420241/src/assets/text-icon.png -------------------------------------------------------------------------------- /src/assets/user.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitInit/pnd-web/807c2216b0c5eaab9bb224f6973b4f30c3420241/src/assets/user.jpeg -------------------------------------------------------------------------------- /src/assets/video-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BitInit/pnd-web/807c2216b0c5eaab9bb224f6973b4f30c3420241/src/assets/video-icon.png -------------------------------------------------------------------------------- /src/components/FileUpload.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 146 | 147 | -------------------------------------------------------------------------------- /src/components/FolderTreeDialog.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/uploader/btn.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 38 | 39 | 57 | -------------------------------------------------------------------------------- /src/components/uploader/common/file-events.js: -------------------------------------------------------------------------------- 1 | const events = ['fileProgress', 'fileSuccess', 'fileComplete', 'fileError'] 2 | 3 | export default events 4 | -------------------------------------------------------------------------------- /src/components/uploader/common/mixins.js: -------------------------------------------------------------------------------- 1 | export const uploaderMixin = { 2 | inject: ['uploader'] 3 | } 4 | 5 | export const supportMixin = { 6 | data () { 7 | return { 8 | support: true 9 | } 10 | }, 11 | mounted () { 12 | this.support = this.uploader.uploader.support 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/uploader/common/utils.js: -------------------------------------------------------------------------------- 1 | export function secondsToStr (temp) { 2 | const years = Math.floor(temp / 31536000) 3 | if (years) { 4 | return years + ' year' + numberEnding(years) 5 | } 6 | const days = Math.floor((temp %= 31536000) / 86400) 7 | if (days) { 8 | return days + ' day' + numberEnding(days) 9 | } 10 | const hours = Math.floor((temp %= 86400) / 3600) 11 | if (hours) { 12 | return hours + ' hour' + numberEnding(hours) 13 | } 14 | const minutes = Math.floor((temp %= 3600) / 60) 15 | if (minutes) { 16 | return minutes + ' minute' + numberEnding(minutes) 17 | } 18 | const seconds = temp % 60 19 | return seconds + ' second' + numberEnding(seconds) 20 | function numberEnding (number) { 21 | return (number > 1) ? 's' : '' 22 | } 23 | } 24 | 25 | export function kebabCase (s) { 26 | return s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`) 27 | } 28 | -------------------------------------------------------------------------------- /src/components/uploader/drop.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 51 | 52 | 65 | -------------------------------------------------------------------------------- /src/components/uploader/file.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 307 | 308 | 443 | -------------------------------------------------------------------------------- /src/components/uploader/files.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 32 | 33 | 43 | -------------------------------------------------------------------------------- /src/components/uploader/list.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 32 | 33 | 43 | -------------------------------------------------------------------------------- /src/components/uploader/unsupport.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | 22 | 29 | -------------------------------------------------------------------------------- /src/components/uploader/uploader.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 143 | 144 | 149 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import ElementUI from 'element-ui' 4 | import 'element-ui/lib/theme-chalk/index.css' 5 | import locale from 'element-ui/lib/locale/lang/zh-CN' 6 | 7 | import router from './router' 8 | import store from './store' 9 | 10 | Vue.use(ElementUI, { locale }) 11 | Vue.config.productionTip = false 12 | 13 | window.eventBus = new Vue() 14 | 15 | new Vue({ 16 | router, 17 | store, 18 | render: h => h(App), 19 | }).$mount('#app') 20 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | 4 | Vue.use(Router) 5 | 6 | const originalPush = Router.prototype.push 7 | Router.prototype.push = function push(location, onResolve, onReject) { 8 | if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject) 9 | return originalPush.call(this, location).catch(err => err) 10 | } 11 | 12 | let router = new Router({ 13 | routes: [ 14 | { 15 | path: '', 16 | redirect: '/folder/0', 17 | component: () => import('@/views/layout/Layout'), 18 | children: [ 19 | { 20 | path: 'folder/:folderId', 21 | component: () => import('@/views/file'), 22 | props: true, 23 | meta: {title: '文件列表'} 24 | }, 25 | { 26 | path: '/system', 27 | component: () => import('@/views/system'), 28 | meta: {title: '系统信息'} 29 | } 30 | ] 31 | }, 32 | { 33 | path: '/video/:fileId', 34 | component: () => import('@/views/video'), 35 | props: true, 36 | meta: {title: '播放视频'} 37 | }, 38 | {path: '*', component: () => import('@/views/404')} 39 | ] 40 | }) 41 | 42 | router.beforeEach((to, _, next) => { 43 | /*路由发生改变修改页面的title */ 44 | if (to.meta.title) { 45 | document.title = to.meta.title 46 | } 47 | next() 48 | }) 49 | 50 | export default router -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | folderId: 0 9 | }, 10 | mutations: { 11 | changeFolderId (state, folderId) { 12 | state.folderId = folderId 13 | } 14 | } 15 | }) -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @mixin linkTo { 2 | text-decoration: none; 3 | color: #fff; 4 | &:hover { 5 | color: #a0d4ff; 6 | } 7 | } -------------------------------------------------------------------------------- /src/styles/layout.scss: -------------------------------------------------------------------------------- 1 | .app-container { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | .el-header{ 8 | padding: 0px; 9 | background-color: #4182f3; 10 | box-shadow: 0 1px 1px rgba(204, 214, 241, 0.897); 11 | line-height: 50px; 12 | z-index: 10; 13 | } 14 | 15 | .el-aside { 16 | border-right: 1px solid #e6e6e6; 17 | } 18 | 19 | .main-container { 20 | height: 100%; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 格式化文件大小, 输出成带单位的字符串 3 | * @method formatSize 4 | * @grammar formatSize( size ) => String 5 | * @grammar formatSize( size, pointLength ) => String 6 | * @grammar formatSize( size, pointLength, units ) => String 7 | * @param {Number} size 文件大小 8 | * @param {Number} [pointLength=2] 精确到的小数点数。 9 | * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. 10 | * @example 11 | * console.log( formatSize( 100 ) ); // => 100B 12 | * console.log( formatSize( 1024 ) ); // => 1.00K 13 | * console.log( formatSize( 1024, 0 ) ); // => 1K 14 | * console.log( formatSize( 1024 * 1024 ) ); // => 1.00M 15 | * console.log( formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G 16 | * console.log( formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB 17 | */ 18 | export const formatSize = ( size, pointLength, units ) => { 19 | if (size === null || size === undefined){ 20 | return undefined 21 | } 22 | var unit; 23 | units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; 24 | while ( (unit = units.shift()) && size > 1024 ) { 25 | size = size / 1024; 26 | } 27 | return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + 28 | unit; 29 | } 30 | 31 | /** 32 | * 格式化时间,输出指定格式的时间 33 | * @param {Number} timestamp 时间戳 34 | * @param {String} format 格式 35 | * @example 36 | * console.log( formatDateTime(1578754162044) ); // => 2020-01-11 22:49:22 37 | */ 38 | export const formatDateTime = (timestamp, format = 'yyyy/MM/dd hh:mm:ss') => { 39 | var date = new Date(timestamp), 40 | o = { 41 | "M+" : date.getMonth() + 1, // month 42 | "d+" : date.getDate(), // day 43 | "h+" : date.getHours(), // hour 44 | "m+" : date.getMinutes(), // minute 45 | "s+" : date.getSeconds(), // second 46 | "q+" : Math.floor((date.getMonth() + 3) / 3), // quarter 47 | "S+" : date.getMilliseconds() 48 | // millisecond 49 | } 50 | 51 | if (/(y+)/.test(format)) { 52 | format = format.replace(RegExp.$1, (date.getFullYear() + "").substr(4 53 | - RegExp.$1.length)); 54 | } 55 | 56 | for (var k in o) { 57 | if (new RegExp("(" + k + ")").test(format)) { 58 | var formatStr=""; 59 | for(var i=1;i<=RegExp.$1.length;i++){ 60 | formatStr+="0"; 61 | } 62 | 63 | var replaceStr=""; 64 | if(RegExp.$1.length == 1){ 65 | replaceStr=o[k]; 66 | }else{ 67 | formatStr=formatStr+o[k]; 68 | var index=("" + o[k]).length; 69 | formatStr=formatStr.substr(index); 70 | replaceStr=formatStr; 71 | } 72 | format = format.replace(RegExp.$1, replaceStr); 73 | } 74 | } 75 | return format; 76 | } -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /src/views/file/index.vue: -------------------------------------------------------------------------------- 1 | 94 | 95 | 337 | 338 | -------------------------------------------------------------------------------- /src/views/layout/Aside.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 49 | 50 | -------------------------------------------------------------------------------- /src/views/layout/Header.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | 20 | -------------------------------------------------------------------------------- /src/views/layout/Layout.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 30 | 31 | -------------------------------------------------------------------------------- /src/views/system/index.vue: -------------------------------------------------------------------------------- 1 | 92 | 93 | 134 | 135 | -------------------------------------------------------------------------------- /src/views/video/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 68 | 69 | --------------------------------------------------------------------------------