浏览代码

资料库版本

yuwenjun1997 2 年之前
父节点
当前提交
00f867ea9c

+ 15 - 8
.env.development

@@ -4,12 +4,15 @@ NODE_ENV = development
 ENV = 'development'
 
 # 测试地址 API接口
-VUE_APP_BASE_API = 'https://zplma-b.caimei365.com'
+# VUE_APP_BASE_API = 'https://zplma-b.caimei365.com'
 # VUE_APP_BASE_API = 'http://192.168.2.68:8012'
+VUE_APP_BASE_API = 'http://192.168.2.67:8012'
 
 # 文件上传 API接口地址
-VUE_APP_UPLOAD_API='https://zplma-b.caimei365.com'
+# VUE_APP_UPLOAD_API='https://zplma-b.caimei365.com'
 # VUE_APP_UPLOAD_API='http://192.168.2.68:8012'
+VUE_APP_UPLOAD_API='http://192.168.2.67:8012'
+
 
 # 二维码生成链接location
 VUE_APP_BASE_SERVER = 'https://www-b.caimei365.com'
@@ -19,13 +22,17 @@ VUE_APP_SOCKET_SERVER = 'wss://zplma-b.caimei365.com/websocket?sessionSource=zpl
 # VUE_APP_SOCKET_SERVER = 'ws://192.168.2.68:8012/websocket?sessionSource=zplm_admin'
 
 # 网站地址
-# VUE_APP_LOCAL = 'http://192.168.2.92:9527'
-VUE_APP_LOCAL = 'http://zplm-b.caimei365.com'
+VUE_APP_LOCAL = 'http://192.168.2.92:9527'
+# VUE_APP_LOCAL = 'http://zplm-b.caimei365.com'
 
 # 认证通页面
-# VUE_APP_WWW_HOST = 'https://192.168.2.92:8888'
-VUE_APP_WWW_HOST = 'https://zp-b.caimei365.com'
+VUE_APP_WWW_HOST = 'https://192.168.2.92:8888'
+# VUE_APP_WWW_HOST = 'https://zp-b.caimei365.com'
 
 # 支付
-# VUE_APP_PAY_LOCAL = 'http://192.168.2.68:18014'
-xVUE_APP_PAY_LOCAL = 'http://zplm-b.caimei365.com
+VUE_APP_PAY_LOCAL = 'http://192.168.2.67:18014'
+# VUE_APP_PAY_LOCAL = 'http://zplm-b.caimei365.com'
+
+# 文件上传路径
+VUE_APP_UPLOAD_DIR = 'dev/authFile/'
+

+ 4 - 1
.env.production

@@ -22,4 +22,7 @@ VUE_APP_PAY_LOCAL = https://core.caimei365.com
 VUE_APP_LOCAL = 'https://zplm.caimei365.com'
 
 # 认证通页面
-VUE_APP_WWW_HOST = 'https://zp.caimei365.com'
+VUE_APP_WWW_HOST = 'https://zp.caimei365.com'
+
+# 文件上传路径
+VUE_APP_UPLOAD_DIR = 'prod/authFile/'

+ 3 - 0
.env.staging

@@ -23,3 +23,6 @@ VUE_APP_LOCAL = 'https://zplm-b.caimei365.com'
 
 # 认证通页面
 VUE_APP_WWW_HOST = 'https://zp-b.caimei365.com'
+
+# 文件上传路径
+VUE_APP_UPLOAD_DIR = 'beta/authFile/'

+ 42 - 0
cert.crt

@@ -0,0 +1,42 @@
+-----BEGIN CERTIFICATE-----
+MIIDVzCCAj+gAwIBAgIGMTI5MDM0MA0GCSqGSIb3DQEBCwUAMF4xEDAOBgNVBAMT
+B1Rlc3QgQ0ExCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYD
+VQQHEw1TYW4gRnJhbmNpc2NvMRAwDgYDVQQKEwdUZXN0IENBMB4XDTIyMDcwMTA5
+NTc0MloXDTIzMDcwMTA5NTc0MlowHDEaMBgGA1UEAxMRMTkyLjE2OC4yLjkyOjg4
+ODgwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyCKKMXf2Y2R1FcKoD
+xyKwk6Goi4oS9+j5JuUtPEcOlInwzX+hxRC0g/QTW2Wl+8BaU5GCBoWCt9/vF06w
+X8KY4RD2fnIfh6/bmagWyrvcHSc7a7OjglGkaHDL8a15pqM+mNuq0iC9BTWJI004
+f3Exg1DdZft3oB+wf3iX+taUamAG+fGDV51B2Ih4m/diU4/InoZOHHUO7PqwjTdc
+OfwUteORmx7d5aUu37LIrKTytdPjXdiD26KJPqNlN5/SGojJPV9xh0s27uZQGpf0
+0GlpI5bOxtZR0D7iKkR63x1gwIAfQU7aLNeYbYzGmUVZeq3BLuopnfsD3xYDf/md
+YwwTAgMBAAGjXTBbMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMB0GA1Ud
+JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAcBgNVHREEFTATghExOTIuMTY4LjIu
+OTI6ODg4ODANBgkqhkiG9w0BAQsFAAOCAQEAHAhCVG5RcKgNQgskqUxCmkS5bA1A
+azpGH6l364scmq3DHFrrB5cYzWlsAiYOUl87S97aqbMQLDQ14Xxq26415AYVH5bZ
+t7R5686hcumPYdWs52nZLSjrOR+zc7lg3lzwZgRLGgXbOk5TmuOZWwwM2czognYu
+5yQsbs1CquLiUWgH3BUAiJ/aKefiN09fF2VgN09DAEvw+YUE9Th6vqVjvudz249m
+V02c9w0LQxSQzb9ZW+rD6CiJNrd70tlBGx7YL4Fl4tgredZj5Zondj4zwYzd2D4M
+hLDcdccNtIoTpU8HuJ5Xxuh7CP8zl4kcCs8m/J1EbTOcpW1heGy4sYsxqg==
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIDXjCCAkagAwIBAgIFNjE0MzQwDQYJKoZIhvcNAQELBQAwXjEQMA4GA1UEAxMH
+VGVzdCBDQTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV
+BAcTDVNhbiBGcmFuY2lzY28xEDAOBgNVBAoTB1Rlc3QgQ0EwHhcNMjIwNzAxMDk1
+NTA1WhcNMjMwNzAxMDk1NTA1WjBeMRAwDgYDVQQDEwdUZXN0IENBMQswCQYDVQQG
+EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
+bzEQMA4GA1UEChMHVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
+ggEBAMkxX6xu9fIL0my7KNS20Sxsnm5BY4h2BKYvN2WW/qFzQh9axBbN//zmey8l
+Xl/lw0CIH+NhD8Mut2aPAVaCCERY71hxpRhMTzTyzV8qMWrsBzaU2LmQ7iG3LIkV
+nAFHAT1/EMXaC37wZ8okWW/hKtWZMnJbvPhunSCZl3rSyV903NxIJ5ekyXYh0Bw3
+Y3CuzWK4TS82SpQ6VAj/qG7TZ/rAiWmaTsaG9X01GprAQkdCw5mnBIYtrNQrxKZ6
+/tIzG0WR/fXbmK9KECRUCMgohbvi8ja3Vv3N+Br5eXQRH8UWvu55USgCT+XpE5A7
+hcNRRULOubMzRl05aVI+vsZBnnECAwEAAaMjMCEwDwYDVR0TAQH/BAUwAwEB/zAO
+BgNVHQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADggEBAA38Wkv5aHNKP0UJtqSg
+eeRiBk26uBOLFP9sEUE5gUVlyIJLmKdGSmP6mkQvtZ3IXVNs/PyuwZU/63+EuT8B
+nz1WThtkVuzK6zxgmnEEnkpILH2qxCgoEQh7dspUIm7fCVUGN3PUxcOi+5d3smgn
+GpePuP5XeXUnooUIBBv6NY7tpiTCBqZ6lXZK50kB1lY5QmYbI9J/IP8lJdluqyOz
+cL6jVQ+VmMPoAYMW7g2C23Z4xTNztn+VXh+wIn97RKZsv+f6n45wWNzSsvt/vVLu
+fikgmAcuxAvzy9xY+qa6/qZJEI5zt6kIhH3e+YGBZGM9EVxr8y5hNh7csP7W23yH
+HrQ=
+-----END CERTIFICATE-----

+ 27 - 0
cert.key

@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAsgiijF39mNkdRXCqA8cisJOhqIuKEvfo+SblLTxHDpSJ8M1/
+ocUQtIP0E1tlpfvAWlORggaFgrff7xdOsF/CmOEQ9n5yH4ev25moFsq73B0nO2uz
+o4JRpGhwy/GteaajPpjbqtIgvQU1iSNNOH9xMYNQ3WX7d6AfsH94l/rWlGpgBvnx
+g1edQdiIeJv3YlOPyJ6GThx1Duz6sI03XDn8FLXjkZse3eWlLt+yyKyk8rXT413Y
+g9uiiT6jZTef0hqIyT1fcYdLNu7mUBqX9NBpaSOWzsbWUdA+4ipEet8dYMCAH0FO
+2izXmG2MxplFWXqtwS7qKZ37A98WA3/5nWMMEwIDAQABAoIBAGFFZIJqzCcZ4OtS
+viFoSOTPFJFBBESO3ORcQvTvLSejMqa1sJlTUWg/fDijRCtnHFZ9uE+hforIKfq4
+8k5dU7YxqhHzTik5+nRyRKuHQxLlfszxcGnSxwab5Z0YQ57m0+RcohB31FlSIkj2
+V7eZsd/KjoEYukdG3mmRi/MFov3dIsSwLRRQMnmT9fsIZkMo8wxLoAVpkLp4hprk
+WsyplvoCgJO9SQN7Mebadcwqf3bRPWddMZbgTGPmejwXpc8BE4cEglt344qutNYi
+gNCAHvjcbMtBve791BAeXoYcH9SizAMt6qL8cCcSBMB1FaJxSWnleB5EFwoPVK/I
+3AVZ3ykCgYEA6OZmWzfsExtW8gdTjW7ByhdzDY/GpEHCe0cmOaaSoErQZCev2920
+W96dFQXapYTCQw6mFXi9HOSFryp621M1p8+sl8jBcHmRPKkcPo3q2W6maPnHXnJ3
+svxV3XtopY5bnyamBGQ0nAQgYqqiymP4YskXVm0e8zAqMrlJfbM1WoUCgYEAw7Ed
+/NQQup46RJwtQhix6FCnmukQL6ybCyxTLqIRkr37aegQj+bcTi6TnWCrsZdeyMkI
+B2uQ48WElVowSCI3ePT9WUApFq8FtrcpxKS9Zo/YWBDoCcXhm6X7a1zfakSqjXdA
+llTU2zcr7ZJPBLBwU8wftoqrl0IKj7ghrjlPK7cCgYEA0WgH9FCt1OgqHufzeiFx
+4PGAo1r48kumQA3TEtbw02sxQDxm5LvNJSVd+78NvvFFceHD915eO5JFmSAK4xVI
+EyCpQLhjJWxI6E/tc1lLJkFVIuIjHoqxkZ9M3R3PE7ySKOEpOJ2YqAcVz2E5idyl
+Qr0oXK80utg9uOncaEcv27kCgYEAmCKsU5ByaQL1/b0FGmukdB2wD6i9YrLn3mnZ
+SVnKG7EDMjSuG5G5GDbxn9o14NzzXVmcmlgvzkQSbSIugwXG5oxgIfDVgMMUyBXh
+rPT+IiwWX3DVVDO4z47cxXzwW+eCSHS7i6Prz3k2931VXSnmSd9ZkEqCWBeJqm3W
+EWKQgEUCgYA9MmMwYE0IkfKZ0WhyM35q430ZQdJj4hGCtCq7FVG8zGgooMmynbx1
+4einKREQCh+VwW1FngfRdLODoD5a74RYyQS0Oo0dppRrkz4+RDYzRbTxtg7q4kFk
+H1eOeEgomIpyNJCdiz3lG8Z6KF7rtlK8TkwBtXTeluliAOGoEx4dtw==
+-----END RSA PRIVATE KEY-----

+ 2 - 0
package.json

@@ -44,6 +44,7 @@
     "@vue/cli-plugin-eslint": "4.4.4",
     "@vue/cli-plugin-unit-jest": "4.4.4",
     "@vue/cli-service": "^4.5.13",
+    "ali-oss": "^6.17.1",
     "autoprefixer": "9.5.1",
     "babel-eslint": "10.1.0",
     "babel-jest": "23.6.0",
@@ -64,6 +65,7 @@
     "serve-static": "1.13.2",
     "svg-sprite-loader": "4.1.3",
     "svgo": "1.2.0",
+    "uuid": "^8.3.2",
     "vue-template-compiler": "2.6.10"
   },
   "browserslist": [

+ 104 - 8
src/api/doc.js

@@ -1,6 +1,102 @@
 import request from '@/utils/request'
+// 新资料库 接口
 
-// ################################################
+// 获取oss初始化数据
+export function fetchOssInitData() {
+  return request({
+    url: '/database/oss/token',
+    method: 'get'
+  })
+}
+
+// 资料库文章列表
+export function fetchDocsList(params) {
+  return request({
+    url: '/database/path/file',
+    method: 'get',
+    params
+  })
+}
+
+// 文件保存
+export function saveFile(data) {
+  return request({
+    url: '/database/oss/upload/result',
+    method: 'post',
+    data
+  })
+}
+
+// 创建文件夹
+export function createPackage(data) {
+  return request({
+    url: '/database/path/package',
+    method: 'post',
+    data
+  })
+}
+
+// 更新文件
+export function updatePackage(data) {
+  return request({
+    url: '/database/update/fileName',
+    method: 'post',
+    data
+  })
+}
+
+// 删除文件
+export function removePackage(data) {
+  return request({
+    url: '/database/delete/file',
+    method: 'post',
+    data
+  })
+}
+
+// 文件移动
+export function movePackage(data) {
+  return request({
+    url: '/database/move/package',
+    method: 'post',
+    data
+  })
+}
+
+// 文件路径面包屑导航
+export function fetchPathCumbs(params) {
+  return request({
+    url: '/database/path/crumbs',
+    method: 'get',
+    params
+  })
+}
+// 获取文件夹结构
+export function fetchDirTree(params) {
+  return request({
+    url: '/database/file/tree',
+    method: 'get',
+    params
+  })
+}
+// 勾选下载文件
+export function downFiles(params) {
+  return request({
+    url: '/database/path/chose/zip',
+    method: 'get',
+    params
+  })
+}
+// 勾选下载文件
+export function fetchFileDetail(params) {
+  return request({
+    url: '/database/file/detail',
+    method: 'get',
+    params
+  })
+}
+
+// ###################旧接口#############################
 
 // 资料库文章列表
 export function getArticleList(params) {
@@ -143,13 +239,13 @@ export function getFileList(params) {
 }
 
 // 资料库保存文件
-export function saveFile(data) {
-  return request({
-    url: '/data/file/save',
-    method: 'post',
-    data
-  })
-}
+// export function saveFile(data) {
+//   return request({
+//     url: '/data/file/save',
+//     method: 'post',
+//     data
+//   })
+// }
 
 // 删除文件
 export function removeFile(data) {

+ 2 - 0
src/router/index.js

@@ -22,6 +22,7 @@ import normalAduit from './module/normal/audit'
 import normalClub from './module/normal/club'
 import normalFeedback from './module/normal/feedback'
 import normalMaterial from './module/normal/material'
+import docsRoutes from './module/normal/docs'
 import normalPersonal from './module/normal/personal'
 import normalPersonnel from './module/normal/personnel'
 import normalVip from './module/normal/vip'
@@ -40,6 +41,7 @@ export const asyncRoutes = [
   ...normalClub,
   ...normalFeedback,
   ...normalMaterial,
+  ...docsRoutes,
   ...normalPersonal,
   ...normalPersonnel,
   ...normalSettings,

+ 43 - 0
src/router/module/normal/docs.js

@@ -0,0 +1,43 @@
+/* Layout */
+import Layout from '@/layout'
+
+// 资料管理页面路由
+const docsRoutes = [
+  {
+    path: '/docs',
+    component: Layout,
+    alwaysShow: true,
+    redirect: '/docs/list',
+    name: 'Docs',
+    meta: { title: '资料库', noCache: true },
+    children: [
+      {
+        path: 'list',
+        component: () => import('@/views/normal/docs'),
+        name: 'DocsList',
+        redirect: '/docs/0/list',
+        meta: { title: '产品资料', noCache: true }
+      },
+      {
+        path: ':fileId/list',
+        component: () => import('@/views/normal/docs'),
+        name: 'DocsAllList',
+        meta: { title: '产品资料', noCache: true }
+      },
+      {
+        path: 'article-edit',
+        component: () => import('@/views/normal/docs/article-edit'),
+        name: 'DocsArticleEdit',
+        meta: { title: '文章编辑', noCache: true }
+      },
+      {
+        path: 'detail',
+        component: () => import('@/views/normal/docs/detail'),
+        name: 'DocsDetail',
+        meta: { title: '资料详情', noCache: true }
+      }
+    ]
+  }
+]
+
+export default docsRoutes

+ 2 - 2
src/utils/tools.js

@@ -111,8 +111,8 @@ export function downLoadWithATag(href) {
   downLink.click()
 }
 
-export function downloadWithUrl(url, name) {
-  return fetch(url)
+export function downloadWithUrl(url, name, options = {}) {
+  return fetch(url, options)
     .then((data) => data.blob())
     .then((res) => {
       const a = document.createElement('a')

+ 15 - 0
src/views/components/DocIcon/icons.js

@@ -0,0 +1,15 @@
+// 动态加载modules
+const loadModules = () => {
+  const context = require.context('./icons', true, /\.png$/)
+  const modules = context.keys().reduce((modules, modulePath) => {
+    const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
+    const value = context(modulePath)
+    modules[moduleName] = value
+    return modules
+  }, {})
+  return { modules, context }
+}
+
+const { modules } = loadModules()
+
+export default modules

二进制
src/views/components/DocIcon/icons/ai.png


二进制
src/views/components/DocIcon/icons/cdr.png


二进制
src/views/components/DocIcon/icons/excel.png


二进制
src/views/components/DocIcon/icons/icon-other.png


二进制
src/views/components/DocIcon/icons/other.png


二进制
src/views/components/DocIcon/icons/package.png


二进制
src/views/components/DocIcon/icons/pdf.png


二进制
src/views/components/DocIcon/icons/ppt.png


二进制
src/views/components/DocIcon/icons/ps.png


二进制
src/views/components/DocIcon/icons/video.png


二进制
src/views/components/DocIcon/icons/word.png


二进制
src/views/components/DocIcon/icons/zip.png


+ 67 - 0
src/views/components/DocIcon/index.vue

@@ -0,0 +1,67 @@
+<template>
+  <div class="doc-icon">
+    <img :src="srcUrl" :width="width" :height="height">
+  </div>
+</template>
+<script>
+import docIcons from './icons.js'
+import suffix from './suffix'
+export default {
+  name: 'DocIcon',
+  props: {
+    width: {
+      type: Number,
+      default: 32
+    },
+    height: {
+      type: Number,
+      default: 32
+    },
+    type: {
+      type: String,
+      default: 'other'
+    },
+    src: {
+      type: String,
+      default: ''
+    }
+  },
+  data() {
+    return {
+      docIcons,
+      typeIndex: ''
+    }
+  },
+  computed: {
+    srcUrl() {
+      return this.src ? this.src : this.docIcons[this.typeIndex]
+    }
+  },
+  watch: {
+    type: {
+      immediate: true,
+      handler: function() {
+        if (suffix.video.includes(this.type)) {
+          this.typeIndex = 'video'
+        } else if (this.docIcons[this.type]) {
+          this.typeIndex = this.type
+        } else {
+          this.typeIndex = 'other'
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.doc-icon {
+  font-size: 0;
+  display: inline-block;
+  vertical-align: middle;
+}
+
+.doc-icon img {
+  display: block;
+}
+</style>

+ 3 - 0
src/views/components/DocIcon/suffix.js

@@ -0,0 +1,3 @@
+const video = ['mp4', 'mov', 'avi', 'qt', 'flv', 'wmv', 'asf', 'mpeg', 'mpg', 'vob', 'mkv', 'asf', 'wmv', 'rm', 'rmvb']
+
+export default { video }

+ 158 - 0
src/views/components/OssUpload/index.vue

@@ -0,0 +1,158 @@
+<template>
+  <div class="oss-upload">
+    <input v-show="false" id="file" ref="ossFileInput" type="file" multiple @change="onFileChange">
+    <!-- 文件上传进度 -->
+    <el-dialog title="上传进度" :visible.sync="visible" width="700px">
+      <el-alert v-if="alterVisiable" title="上传记录将在关闭浏览器后清除" type="warning" @close="onAlertClose" />
+      <div class="oss-upload__progress">
+        <el-table :data="fileList" style="width: 100%" :height="tableHeight">
+          <el-table-column label="文件名" prop="fileName" align="center" />
+          <el-table-column label="大小" width="120" align="center">
+            <template slot-scope="{ row }">
+              <span>{{ row.size | fileSize }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="progress" label="进度" width="180" align="center">
+            <template slot-scope="{ row }">
+              <el-progress :percentage="row.percentage" :show-text="false" :status="breakStatus(row.status)" />
+            </template>
+          </el-table-column>
+          <el-table-column prop="status" label="状态" width="100" align="center">
+            <template slot-scope="{ row }">
+              <span v-if="row.status === 0">上传失败</span>
+              <span v-else-if="row.status === 1">上传成功</span>
+              <span v-else>上传中</span>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { v4 as uuidv4 } from 'uuid'
+import { upload } from './upload'
+export default {
+  name: 'OssUpload',
+  filters: {
+    fileSize(size) {
+      size = Math.round(size / 1024)
+      if (size < 1024) return size + 'KB'
+      size = Math.round(size / 1024)
+      if (size < 1024) return size + 'MB'
+      return Math.round(size / 1024) + 'GB'
+    }
+  },
+  props: {
+    onProgress: {
+      type: Function,
+      default: null
+    },
+    onSuccess: {
+      type: Function,
+      default: null
+    },
+    onFaild: {
+      type: Function,
+      default: null
+    }
+  },
+  data() {
+    return {
+      alterVisiable: false,
+      visible: false,
+      fileList: []
+    }
+  },
+  computed: {
+    count() {
+      return this.fileList.length
+    },
+    tableHeight() {
+      return this.count > 7 ? '400' : 'auto'
+    }
+  },
+  created() {
+    const flag = localStorage.getItem('alert-confirm')
+    this.alterVisiable = !flag
+  },
+  methods: {
+    open() {
+      this.visible = true
+    },
+    close() {
+      this.visible = false
+    },
+    breakStatus(status) {
+      if (status === 0) return 'exception'
+      if (status === 1) return 'success'
+      return undefined
+    },
+    chooseFile() {
+      this.$nextTick(() => {
+        this.$refs.ossFileInput.click()
+      })
+    },
+    onFileChange(e) {
+      const fileList = Array.from(e.target.files).map((file) => this.generageRowfile(file))
+      this.fileList = [...this.fileList, ...fileList]
+      this.visible = true
+      upload(this.fileList, {
+        success: this.success,
+        faild: this.faild,
+        progress: this.progress
+      })
+    },
+    generageRowfile(file) {
+      return {
+        uuid: uuidv4(),
+        fileName: file.name,
+        size: file.size,
+        type: file.type,
+        file: file,
+        percentage: 0,
+        status: -1, // 0 上次失败 1 上传成功 2 上传中 -1 待上传
+        response: null,
+        url: '',
+        checkpoint: null
+      }
+    },
+    // 上传成功
+    success(file, response, resultUrl) {
+      file.response = response
+      file.status = 1
+      file.url = resultUrl
+      this.onSuccess && this.onSuccess(file, this.fileList)
+    },
+    // 上传进度
+    progress(file, response) {
+      const { p, cpt, res } = response
+      file.percentage = p * 100
+      file.checkpoint = cpt
+      file.response = res
+      file.status = 2
+      this.onProgress && this.onProgress(file, this.fileList)
+    },
+    // 上传失败
+    faild(file, response) {
+      file.response = response
+      file.status = 0
+      this.onFaild && this.onFaild(file, this.fileList)
+    },
+    // 警告框关闭
+    onAlertClose() {
+      localStorage.setItem('alert-confirm', true)
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+.oss-upload__progress {
+  border: 1px solid #eee;
+  border-bottom: 0;
+}
+
+.el-alert {
+  margin-bottom: 12px;
+}
+</style>

+ 74 - 0
src/views/components/OssUpload/upload.js

@@ -0,0 +1,74 @@
+import { fetchOssInitData } from '@/api/doc'
+import OSS from 'ali-oss'
+let client = null
+let baseFileUrl = ''
+
+export async function initOssClient() {
+  try {
+    const res = await fetchOssInitData()
+    if (res.code) return
+    const { accessKeyId, securityToken, bucket, accessKeySecret } = res.data
+    const config = {
+      // 以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
+      region: 'oss-cn-shenzhen',
+      accessKeyId,
+      accessKeySecret,
+      stsToken: securityToken,
+      bucket
+    }
+    client = new OSS(config)
+    baseFileUrl = `https://${bucket}.${config.region}.aliyuncs.com/`
+
+    setInterval(() => {
+      initOssClient()
+    }, 1000 * 60 * 60)
+  } catch (error) {
+    console.log(error)
+  }
+}
+
+async function multipartUpload(options, callback) {
+  if (!client) {
+    await initOssClient()
+  }
+  if (options.status > -1) return
+  try {
+    // 指定上传到examplebucket的Object名称,例如exampleobject.txt。
+    const name =
+      process.env.VUE_APP_UPLOAD_DIR +
+      options.fileName.replace(/([^\\/]+)\.([^\\/]+)/i, function(match, $1, $2) {
+        options.uuid = options.uuid + '.' + $2
+        return options.uuid
+      })
+    // 分片上传
+    const res = await client.multipartUpload(name, options.file, {
+      // 获取分片上传进度、断点和返回值。
+      progress: (p, cpt, res) => {
+        callback.progress && callback.progress(options, { p, cpt, res })
+      },
+      // 设置并发上传的分片数量。
+      parallel: 10,
+      // 设置分片大小。默认值为1 MB,最小值为100 KB。
+      partSize: 1024 * 1024,
+      // 文件类型
+      mime: options.type,
+
+      headers: {
+        // 指定初始化分片上传时是否覆盖同名Object。此处设置为true,表示禁止覆盖同名Object。
+        'x-oss-forbid-overwrite': 'true'
+      }
+    })
+    // const url = client.signatureUrl(res.name)
+    // console.log(res.name) baseFileUrl + res.name
+    callback.success && callback.success(options, res, baseFileUrl + res.name)
+  } catch (err) {
+    callback.success && callback.faild(options, err)
+  }
+}
+
+// 上传
+export function upload(fileList, callback) {
+  for (let i = 0; i < fileList.length; i++) {
+    multipartUpload(fileList[i], callback)
+  }
+}

+ 149 - 0
src/views/normal/docs/article-edit.vue

@@ -0,0 +1,149 @@
+<template>
+  <div v-loading="isLoading" class="page-form-container">
+    <el-form ref="formRef" label-width="120px" :model="formData" :rules="formRules">
+      <el-form-item label="文章类型:" prop="articleType">
+        <el-radio-group v-model="formData.articleType">
+          <el-radio :label="1">自主</el-radio>
+          <el-radio :label="2">第三方</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="标题:" prop="articleTitle">
+        <el-input v-model="formData.articleTitle" placeholder="请输入文章标题" maxlength="50" show-word-limit />
+      </el-form-item>
+      <el-form-item label="头图:" prop="articleImage">
+        <upload-image
+          tip-title="128px * 128px"
+          :image-list="imageList"
+          @success="handleUploadImageSussces"
+          @remove="handleRemoveImage"
+        />
+        <el-input v-show="false" v-model="formData.articleImage" />
+      </el-form-item>
+      <template v-if="formData.articleType === 1">
+        <el-form-item label="文章内容:" prop="articleContent">
+          <tinymce v-model="formData.articleContent" :token="token" :action="action" :height="300" />
+          <el-input v-show="false" v-model="formData.articleContent" />
+        </el-form-item>
+      </template>
+      <template v-else>
+        <el-form-item label="文章链接:" prop="articleLink">
+          <el-input v-model="formData.articleLink" placeholder="请输入链接" />
+        </el-form-item>
+      </template>
+    </el-form>
+    <div class="control-box center">
+      <el-button type="primary" @click="submit">保存</el-button>
+      <el-button type="warning" @click="navigateBack()">返回</el-button>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import UploadImage from '@/components/UploadImage'
+import Tinymce from '@/components/Tinymce'
+import { getToken } from '@/utils/auth'
+import { saveArticle, getArticleDeatil } from '@/api/doc'
+export default {
+  components: { UploadImage, Tinymce },
+  data() {
+    return {
+      editType: 0, // 编辑模式 1:添加文章  2:修改文章
+      formData: {
+        articleId: '', // 文章id
+        articleType: 1, // 文章类型
+        articleTitle: '', // 文章标题
+        articleImage: '', // 文章封面
+        articleContent: '', // 文章内容
+        articleLink: '',
+        parentId: ''
+      },
+      // 表单验证规则
+      formRules: {
+        articleTitle: [
+          { required: true, message: '文章标题不能为空', trigger: 'blur' },
+          { min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' }
+        ],
+        articleImage: [{ required: true, message: '文章必须上传封面', trigger: 'change' }],
+        articleContent: [{ required: true, message: '文章内容不能为空', trigger: 'change' }],
+        articleLink: [{ required: true, message: '文章链接不能为空', trigger: 'blur' }]
+      },
+      // 表单是否处于加载状态
+      isLoading: true,
+      // 当前用户登录的token
+      token: '',
+      // 富文本框上传图片的接口
+      action: process.env.VUE_APP_UPLOAD_API + '/upload/image',
+      // 封面图片列表
+      imageList: []
+    }
+  },
+  computed: {
+    ...mapGetters(['authUserId'])
+  },
+  created() {
+    this.token = getToken()
+    this.formData.articleId = this.$route.query.articleId || ''
+    this.formData.parentId = this.$route.query.parentId || 0
+    this.editType = this.formData.articleId ? 2 : 1
+    this.initFormData()
+  },
+  mounted() {
+    setTimeout(() => {
+      this.isLoading = false
+    }, 500)
+  },
+  methods: {
+    // 保存文章
+    submit() {
+      this.formData.authUserId = this.authUserId
+      this.$refs.formRef.validate((valid) => {
+        if (!valid) return
+        this.isLoading = true
+        saveArticle(this.formData)
+          .then((res) => {
+            console.log(res)
+            if (res.code !== 0) return
+            this.$message.success(res.data)
+            // 如果保存文章成功就要关闭当前页面
+            this.$store.dispatch('tagsView/delView', this.$route)
+            this.$router.back()
+          })
+          .finally(() => {
+            this.isLoading = false
+          })
+      })
+    },
+    // 初始化表单回显数据
+    initFormData() {
+      if (!this.formData.articleId) return
+      getArticleDeatil({ articleId: this.formData.articleId }).then((res) => {
+        for (const key in this.formData) {
+          if (Object.hasOwnProperty.call(this.formData, key)) {
+            if (res.data[key]) {
+              this.formData[key] = res.data[key]
+            }
+          }
+        }
+        this.imageList = [{ name: '文章封面', url: this.formData.articleImage }]
+        console.log(res)
+      })
+    },
+    // 封面图片上传成功
+    handleUploadImageSussces({ response, file, fileList }) {
+      this.imageList = fileList
+      this.formData.articleImage = response.data
+    },
+    handleRemoveImage({ response, file, fileList }) {
+      this.imageList = fileList
+      this.formData.articleImage = ''
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.page-form-container {
+  width: 900px;
+}
+</style>

+ 186 - 0
src/views/normal/docs/detail.vue

@@ -0,0 +1,186 @@
+<template>
+  <div class="app-container">
+    <div v-if="fileData" class="doc-header">
+      <div class="info">
+        <div class="cover">
+          <doc-icon :type="fileData.fileType" :width="32" :height="32" :src="fileData.screenshot" />
+          <!-- <img src="" alt=""> -->
+        </div>
+        <div class="content">
+          <div class="name">{{ fileData.fileName }}</div>
+          <div class="row">
+            <span class="date">时间:{{ fileData.saveTime | formatTime }}</span>
+            <span class="size">大小:{{ fileData.fileSize | fileSize }}</span>
+          </div>
+        </div>
+      </div>
+      <div><el-button type="primary" @click="onDownload">下载</el-button></div>
+    </div>
+    <div class="doc-content">
+      <img v-if="fileType === 'image'" :src="fileData.ossUrl" alt="">
+      <video v-else-if="fileType === 'video'" :src="fileData.ossUrl" />
+      <div v-else class="other">
+        <doc-icon type="icon-other" :width="64" :height="64" />
+        <div class="name">{{ fileData.fileName }}</div>
+        <el-button size="small" type="primary" @click="onDownload">下载并打开</el-button>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import DocIcon from '@/views/components/DocIcon'
+import { fetchFileDetail } from '@/api/doc'
+import { downloadWithUrl } from '@/utils/tools'
+export default {
+  components: {
+    DocIcon
+  },
+  filters: {
+    fileSize(size) {
+      size = Math.round(size / 1024)
+      if (size < 1024) return size + 'KB'
+      size = Math.round(size / 1024)
+      if (size < 1024) return size + 'MB'
+      return Math.round(size / 1024) + 'GB'
+    }
+  },
+  data() {
+    return {
+      fileId: '',
+      fileData: {}
+    }
+  },
+  computed: {
+    fileType() {
+      const mime = this.fileData.mime
+      if (!mime) return 'other'
+      if (mime.indexOf('image') > -1) return 'image'
+      if (mime.indexOf('video') > -1) return 'video'
+      return 'other'
+    }
+  },
+  created() {
+    this.fileId = this.$route.query.id
+    this.fetchFileDetail()
+  },
+  methods: {
+    async fetchFileDetail() {
+      try {
+        const res = await fetchFileDetail({ fileId: this.fileId })
+        this.fileData = res.data
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 下载文件
+    async onDownload() {
+      const confirmText = '文件'
+      const text = await this.$confirm(`确认下载所选${confirmText}?`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).catch(() => {
+        this.exportClubList = []
+        this.$message.info('已取消操作')
+      })
+      if (text !== 'confirm') return
+      let notification = null
+      notification = this.$notify({
+        title: '提示',
+        message: `正在下载${confirmText},请勿重复操作!`,
+        duration: 0
+      })
+      // 下载链接
+      let downUrl = ''
+      const target = this.fileData
+      if (target.packageType > 0) {
+        downUrl = `${process.env.VUE_APP_BASE_API}/download/file?ossName=${target.ossName}&fileName=${target.fileName}`
+      } else {
+        downUrl = `${process.env.VUE_APP_BASE_API}/database/path/package/zip?fileId=${target.id}&fileName=${target.fileName}`
+      }
+      const options = {
+        headers: {
+          'X-Token': this.$store.getters.token
+        }
+      }
+      downloadWithUrl(downUrl, confirmText, options)
+        .catch((err) => {
+          console.log(err)
+          this.$message.error(`下载${confirmText}失败`)
+        })
+        .finally(() => {
+          notification.close()
+        })
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.app-container {
+  padding-top: 40px;
+}
+.doc-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 64px;
+
+  .info {
+    display: flex;
+    align-items: center;
+
+    .cover {
+      img {
+        display: block;
+        width: 48px;
+        height: 48px;
+        background: skyblue;
+      }
+    }
+
+    .content {
+      margin-left: 24px;
+      .name {
+        font-size: 16px;
+        color: #282828;
+        margin-bottom: 8px;
+      }
+      .date,
+      .size {
+        font-size: 14px;
+        color: #999999;
+      }
+
+      .size {
+        margin-left: 32px;
+      }
+    }
+  }
+}
+
+.doc-content {
+  padding-top: 80px;
+  display: flex;
+  justify-content: center;
+  img,
+  video {
+    display: block;
+    height: 560px;
+    // background: #000;
+  }
+
+  .other {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    flex-direction: column;
+    margin-top: 200px;
+
+    .name {
+      font-size: 16px;
+      color: #666666;
+      margin: 16px 0 24px;
+    }
+  }
+}
+</style>

+ 492 - 0
src/views/normal/docs/index.vue

@@ -0,0 +1,492 @@
+<template>
+  <div v-loading="listLoading" class="app-container page-component__scroll">
+    <div class="filter-container">
+      <div v-if="selectedCount" class="filter-control">
+        <el-button
+          type="primary"
+          size="small"
+          icon="el-icon-edit"
+          plain
+          :disabled="selectedCount > 1"
+          @click="onEditFile"
+        >编辑</el-button>
+        <el-button type="primary" size="small" icon="el-icon-rank" plain @click="onMoveFile">移动</el-button>
+        <el-button type="primary" size="small" icon="el-icon-download" plain @click="onDownload">下载</el-button>
+        <el-button type="primary" size="small" icon="el-icon-delete" plain @click="onDelete">删除</el-button>
+      </div>
+      <div v-else class="filter-control">
+        <el-button type="primary" size="small" icon="el-icon-upload2" plain @click="onChooseFile">上传文件</el-button>
+        <el-button
+          type="primary"
+          size="small"
+          icon="el-icon-folder-add"
+          plain
+          @click="onCreateDir"
+        >新建文件夹</el-button>
+        <el-button
+          type="primary"
+          size="small"
+          icon="el-icon-s-promotion"
+          plain
+          @click="onPublishArticle"
+        >发布文章</el-button>
+        <el-button
+          type="primary"
+          size="small"
+          icon="el-icon-alarm-clock"
+          plain
+          @click="onUploadHistory"
+        >上传记录</el-button>
+      </div>
+    </div>
+    <!-- 面包屑 -->
+    <el-breadcrumb separator-class="el-icon-arrow-right">
+      <el-breadcrumb-item :to="{ path: '/docs/0/list' }">全部文件</el-breadcrumb-item>
+      <template v-for="(item, index) in crumbList">
+        <template v-if="index === crumbList.length - 1">
+          <el-breadcrumb-item :key="item.id">
+            <span class="cell">{{ item.fileName }}</span>
+          </el-breadcrumb-item>
+        </template>
+        <template v-else>
+          <el-breadcrumb-item :key="item.id" :to="{ path: `/docs/${item.id}/list` }">
+            <span>{{ item.fileName }}</span>
+          </el-breadcrumb-item>
+        </template>
+      </template>
+    </el-breadcrumb>
+    <!-- 列表 -->
+    <el-table :data="list" style="width: 100%" @selection-change="onSelectionChange">
+      <el-table-column type="selection" align="center" width="50" />
+      <el-table-column label="文件名">
+        <template slot-scope="{ row }">
+          <doc-icon v-if="!listLoading" :type="row.fileType" :src="row.screenshot" />
+          <span class="file-name" @click.stop="onRowClick(row)">{{ row.fileName }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="createTime" label="时间" width="160">
+        <template slot-scope="{ row }">{{ row.saveTime | formatTime }}</template>
+      </el-table-column>
+      <el-table-column label="大小" width="120">
+        <template slot-scope="{ row }">
+          <span v-if="row.packageType > 0">{{ row.fileSize | fileSize }}</span>
+          <span v-else>-</span>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 文件上传 -->
+    <OssUpload ref="ossUpload" :on-success="onSuccess" />
+    <!-- 创建文件夹弹窗 -->
+    <el-dialog :title="editText" :visible.sync="editDialog" width="30%" :close-on-click-modal="false" @close="onCancel">
+      <el-form ref="ruleForm" :model="formData" :rules="rules" label-width="80">
+        <el-form-item prop="fileName" label="名称:">
+          <el-input v-model="formData.fileName" placeholder="请输入文件名" />
+        </el-form-item>
+      </el-form>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="editDialog = false">取 消</el-button>
+        <el-button type="primary" @click="onSubmit">确 定</el-button>
+      </span>
+    </el-dialog>
+
+    <!-- 移动文件 -->
+    <el-dialog title="选择移动位置" :visible.sync="moveDialog" width="60%" :close-on-click-modal="false">
+      <el-button icon="el-icon-arrow-left" :disabled="lastDisabled" size="mini" @click="currentIndex--" />
+      <el-button icon="el-icon-arrow-right" :disabled="nextDisabled" size="mini" @click="currentIndex++" />
+      <el-table
+        :data="filterSameDir(moveCurrentDir.childList)"
+        style="width: 100%"
+        height="300"
+        empty-text="该目录为空"
+      >
+        <el-table-column label="文件名">
+          <template slot-scope="{ row }">
+            <doc-icon v-if="!listLoading" :type="row.fileType" />
+            <span class="file-name" @click.stop="onMoveRowClick(row)">{{ row.fileName }}</span>
+          </template>
+        </el-table-column>
+      </el-table>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="onMoveCancel">取 消</el-button>
+        <el-button type="primary" @click="onSubmitMove">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import DocIcon from '@/views/components/DocIcon'
+import OssUpload from '@/views/components/OssUpload'
+import {
+  createPackage,
+  fetchDocsList,
+  removePackage,
+  saveFile,
+  updatePackage,
+  fetchPathCumbs,
+  fetchDirTree,
+  movePackage
+} from '@/api/doc'
+import { downloadWithUrl } from '@/utils/tools'
+export default {
+  name: 'DocsAllList',
+  components: { DocIcon, OssUpload },
+  filters: {
+    fileSize(size) {
+      size = Math.round(size / 1024)
+      if (size < 1024) return size + 'KB'
+      size = Math.round(size / 1024)
+      if (size < 1024) return size + 'MB'
+      return Math.round(size / 1024) + 'GB'
+    }
+  },
+  data() {
+    return {
+      selectionList: [], // 选中列表
+      list: [],
+      fileId: 0,
+      editDialog: false,
+      moveDialog: false,
+      listLoading: false,
+      formData: {
+        fileName: '',
+        fileId: ''
+      },
+      rules: {
+        fileName: [{ required: true, message: '文件名不能为空', trigger: ['blur'] }]
+      },
+      editType: '',
+      current: null,
+      crumbList: [],
+      currentDirStack: [],
+      currentIndex: 0
+    }
+  },
+  computed: {
+    selectedCount() {
+      return this.selectionList.length
+    },
+    editText() {
+      const texts = {
+        editType: '创建文件夹',
+        editFile: '修改文件'
+      }
+      return texts[this.editType]
+    },
+    moveCurrentDir() {
+      return this.currentDirStack[this.currentIndex] || []
+    },
+    lastDisabled() {
+      return this.currentIndex === 0 || this.currentDirStack.length === 0
+    },
+    nextDisabled() {
+      return this.currentIndex === this.currentDirStack.length - 1 || this.currentDirStack.length === 0
+    }
+  },
+  created() {
+    this.init()
+  },
+  methods: {
+    // 排除相同文件夹
+    filterSameDir(dirList = []) {
+      return dirList.filter((item) => {
+        return !this.selectionList.find((file) => file.id === item.id)
+      })
+    },
+    // 获取文件夹结构
+    async fetchDirTree() {
+      try {
+        const res = await fetchDirTree()
+        res.data.fileName = '根目录'
+        this.currentDirStack.push(res.data)
+        this.currentIndex = this.currentDirStack.length - 1
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 处理面包屑
+    generateCrumb(node) {
+      const list = []
+      if (!node) return list
+      function recursive(node) {
+        list.push(node)
+        if (node.childNode) {
+          recursive(node.childNode)
+        }
+      }
+      recursive(node)
+      return list
+    },
+
+    // 页面初始化
+    init() {
+      this.fileId = parseInt(this.$route.params.fileId)
+      if (!this.fileId && this.fileId !== 0) return
+      this.fetchDocsList()
+      this.fetchPathCumbs()
+    },
+
+    // 获取面包屑导航
+    async fetchPathCumbs() {
+      try {
+        const res = await fetchPathCumbs({ fileId: this.fileId })
+        this.crumbList = this.generateCrumb(res.data)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 获取资料库列表
+    async fetchDocsList() {
+      try {
+        this.listLoading = true
+        const res = await fetchDocsList({ fileId: this.fileId })
+        this.list = res.data || []
+        this.listLoading = false
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 保存上传文件
+    async saveUploadFile(data) {
+      try {
+        await saveFile(data)
+        this.fetchDocsList()
+        this.$message.success('文件上传成功')
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 取消保存
+    onCancel() {
+      this.formData.fileId = ''
+      this.formData.fileName = ''
+      this.$refs.ruleForm.clearValidate()
+    },
+    // 保存
+    async onSubmit() {
+      const actions = {
+        createDir: this.onSaveDir,
+        editFile: this.onUpdateFile
+      }
+      try {
+        await this.$refs.ruleForm.validate()
+        actions[this.editType]()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 保存文件夹
+    async onSaveDir() {
+      try {
+        this.formData.fileId = this.fileId
+        await createPackage(this.formData)
+        this.fetchDocsList()
+        this.editDialog = false
+        this.$message.success('文件夹创建成功')
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 修改文件名
+    async onUpdateFile() {
+      try {
+        await updatePackage(this.formData)
+        this.fetchDocsList()
+        this.editDialog = false
+        this.$message.success('文件名修改成功')
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 选择文件并上传
+    onChooseFile() {
+      this.$refs.ossUpload.chooseFile()
+    },
+    // 上传成功
+    onSuccess(file, fileList) {
+      this.saveUploadFile({
+        fileName: file.fileName,
+        ossName: file.uuid,
+        ossUrl: file.url,
+        fileSize: file.size,
+        parentId: this.fileId,
+        mime: file.type
+      })
+    },
+    // 查看历史记录
+    onUploadHistory() {
+      this.$refs.ossUpload.open()
+    },
+    // 创建文件夹
+    onCreateDir() {
+      this.editType = 'createDir'
+      this.editDialog = true
+    },
+    // 发布文章
+    onPublishArticle() {
+      const url = `/docs/article-edit?parentId=${this.fileId}`
+      this.$router.push(url)
+    },
+    // 编辑文件
+    onEditFile() {
+      this.current = this.selectionList[0]
+      if (this.current.fileType === 'article') {
+        // 文章
+        const url = `/docs/article-edit?parentId=${this.current.parentId}&articleId=${this.current.articleId}`
+        this.$router.push(url)
+      } else {
+        this.editType = 'editFile'
+        this.editDialog = true
+        this.formData.fileName = this.current.fileName
+        this.formData.fileId = this.current.id
+      }
+    },
+    // 移动文件
+    onMoveFile() {
+      this.currentDirStack = []
+      this.currentIndex = 0
+      this.fetchDirTree()
+      this.moveDialog = true
+    },
+    // 移动取消
+    onMoveCancel() {
+      this.moveDialog = false
+      this.fetchDocsList()
+    },
+    // 移动确认
+    async onSubmitMove() {
+      const current = this.currentDirStack[this.currentIndex]
+      const filedIds = this.selectionList.map((file) => file.id).join(',')
+      try {
+        await movePackage({ fileId: filedIds, parentId: current.id })
+        this.moveDialog = false
+        this.fetchDocsList()
+        this.$message.success('文件移动成功')
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 下载文件
+    async onDownload() {
+      const confirmText = '文件'
+      const filedIds = this.selectionList.map((file) => file.id).join(',')
+      const text = await this.$confirm(`确认下载所选${confirmText}?`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).catch(() => {
+        this.exportClubList = []
+        this.$message.info('已取消操作')
+      })
+      if (text !== 'confirm') return
+      let notification = null
+      notification = this.$notify({
+        title: '提示',
+        message: `正在下载${confirmText},请勿重复操作!`,
+        duration: 0
+      })
+      // 下载链接
+      let downUrl = ''
+      if (this.selectionList.length === 1) {
+        const target = this.selectionList[0]
+        if (target.packageType > 0) {
+          downUrl = `${process.env.VUE_APP_BASE_API}/download/file?ossName=${target.ossName}&fileName=${target.fileName}`
+        } else {
+          downUrl = `${process.env.VUE_APP_BASE_API}/database/path/package/zip?fileId=${target.id}&fileName=${target.fileName}`
+        }
+      } else {
+        // 使用a链接下载
+        downUrl = `${process.env.VUE_APP_BASE_API}/database/path/chose/zip?fileId=${filedIds}`
+      }
+      downloadWithUrl(downUrl, confirmText, {
+        headers: {
+          'X-Token': this.$store.getters.token
+        }
+      })
+        .then((res) => {
+          this.fetchDocsList()
+        })
+        .catch((err) => {
+          console.log(err)
+          this.$message.error(`下载${confirmText}失败`)
+        })
+        .finally(() => {
+          notification.close()
+        })
+    },
+    // 删除文件
+    async onDelete(file) {
+      const tip = file.packageType > 0 ? '确定删除该文件夹及其文件夹内的全部文件?' : '确定删除该文件?'
+      let confirm = ''
+      try {
+        confirm = await this.$confirm(tip, '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+      } catch (error) {
+        console.log(error)
+        this.$message.info('已取消操作')
+      }
+      if (confirm !== 'confirm') return false
+      // 获取要删除的文件id
+      const filedIds = this.selectionList.map((file) => file.id).join(',')
+      try {
+        await removePackage({ fileId: filedIds })
+        this.fetchDocsList()
+        this.$message.success('删除成功')
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 表格内容勾选
+    onSelectionChange(val) {
+      this.selectionList = val
+    },
+    // 文件点击
+    onRowClick(row) {
+      if (row.packageType === 1) {
+        // 其它文件
+        console.log('详情')
+        this.$router.push(`/docs/detail?id=${row.id}`)
+      } else {
+        this.$router.push(`/docs/${row.id}/list`)
+      }
+    },
+    // 移动位置点击
+    onMoveRowClick(row) {
+      if (this.currentIndex === this.currentDirStack.length - 1) {
+        this.currentDirStack.push(row)
+        this.currentIndex = this.currentDirStack.length - 1
+      } else {
+        this.currentDirStack.splice(this.currentIndex + 1, this.currentDirStack.length)
+        this.currentDirStack.push(row)
+        this.currentIndex = this.currentDirStack.length - 1
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.file-name {
+  font-size: 14px;
+  color: #666666;
+  margin-left: 25px;
+  cursor: pointer;
+  line-height: 32px;
+}
+
+.file-name:hover {
+  color: #1890ff;
+}
+
+.el-breadcrumb {
+  margin: 16px 0;
+  .cell {
+    color: #999;
+  }
+}
+</style>

+ 4 - 0
vue.config.js

@@ -36,6 +36,10 @@ module.exports = {
       warnings: false,
       errors: true
     }
+    // https: {
+    //   key: './cert.key',
+    //   cert: './cert.crt'
+    // }
   },
   configureWebpack: {
     // provide the app's title in webpack's name field, so that