Browse Source

认证通资料库版本上线

yuwenjun1997 2 years ago
parent
commit
f9171d6c20

+ 5 - 5
.env.development

@@ -2,13 +2,13 @@
 EVN = 'development'
 
 # 网站地址
-# LOCALHOSE = 'https://zp-b.caimei365.com'
-LOCALHOSE = 'http://192.168.2.92:8888'
+LOCALHOSE = 'https://zp-b.caimei365.com'
+# LOCALHOSE = 'http://192.168.2.92:8888'
 
 # 接口api地址
-# BASE_URL = 'https://zplma-b.caimei365.com'
+BASE_URL = 'https://zplma-b.caimei365.com'
 # BASE_URL = 'http://192.168.2.68:8012'
-BASE_URL = 'http://192.168.2.67:8012'
+# BASE_URL = 'http://192.168.2.67:8012'
 
 # 静态资源文件地址
 STATIC_URL = 'https://static.caimei365.com/www/authentic'
@@ -23,4 +23,4 @@ HOST = '192.168.2.92'
 PORT = '8888'
 
 # HTTPS flag
-HTTPS = true
+HTTPS = false

+ 13 - 4
apis/index.js

@@ -137,10 +137,17 @@ export default ($axios) => {
   // 查询授权商品列表
   const fetchDetialBySnCode = (params = {}) =>
     $axios.get('/wx/auth/product/info', { params })
-  
-  // 查询授权商品列表
+
+  // 获取文件列表
   const fetchDocsList = (params = {}) =>
-    $axios.get('/wx/data/path/file', { params })  
+    $axios.get('/notoken/path/file', { params })
+  // 获取面包屑
+  const fetchCrumbsList = (params = {}) =>
+    $axios.get('/notoken/path/crumbs', { params })
+
+  // 获取面包屑
+  const fetchFileDetail = (params = {}) =>
+    $axios.get('/wx/data/file/detail', { params })
 
   return {
     customLogin,
@@ -185,6 +192,8 @@ export default ($axios) => {
     fetchProductDetails,
     assistantBaidu,
     fetchDetialBySnCode,
-    fetchDocsList
+    fetchDocsList,
+    fetchCrumbsList,
+    fetchFileDetail
   }
 }

+ 15 - 0
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

BIN
components/DocIcon/icons/ai.png


BIN
components/DocIcon/icons/cdr.png


BIN
components/DocIcon/icons/excel.png


BIN
components/DocIcon/icons/icon-other.png


BIN
components/DocIcon/icons/other.png


BIN
components/DocIcon/icons/package.png


BIN
components/DocIcon/icons/pdf.png


BIN
components/DocIcon/icons/ppt.png


BIN
components/DocIcon/icons/ps.png


BIN
components/DocIcon/icons/video.png


BIN
components/DocIcon/icons/word.png


BIN
components/DocIcon/icons/zip.png


+ 73 - 0
components/DocIcon/index.vue

@@ -0,0 +1,73 @@
+<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 (suffix.word.includes(this.type)) {
+          this.typeIndex = 'word'
+        } else if (suffix.ppt.includes(this.type)) {
+          this.typeIndex = 'ppt'
+        } else if (suffix.excel.includes(this.type)) {
+          this.typeIndex = 'excel'
+        } 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>

+ 9 - 0
components/DocIcon/suffix.js

@@ -0,0 +1,9 @@
+const video = ['mp4', 'mov', 'avi', 'qt', 'flv', 'wmv', 'asf', 'mpeg', 'mpg', 'vob', 'mkv', 'asf', 'wmv', 'rm', 'rmvb']
+
+const word = ['doc', 'docx']
+
+const ppt = ['ppt', 'pptx']
+
+const excel = ['xls', 'xlsx']
+
+export default { video, word, ppt, excel }

+ 1 - 1
layouts/app-ross.vue

@@ -125,7 +125,7 @@ export default {
         {
           id: 2,
           name: '产品资料',
-          path: '/docs/10',
+          path: '/docs/0',
           icon: 'icon-doc',
         },
         {

+ 37 - 0
mixins/articleDetail.js

@@ -0,0 +1,37 @@
+export default {
+  data() {
+    return {
+      articleId: '',
+      articleInfo: {},
+      imageList: [],
+    }
+  },
+  computed: {
+    html() {
+      const html = this.articleInfo.articleContent
+      if (html) {
+        return html.replace(/href=/gi, '')
+      }
+      return ''
+    },
+  },
+  mounted() {
+    this.initData()
+  },
+  methods: {
+    initData() {
+      this.articleId = parseInt(this.$route.query.articleId)
+      this.fetchArticleDetail()
+    },
+    async fetchArticleDetail() {
+      try {
+        const res = await this.$http.api.getArticleDetail({
+          articleId: this.articleId,
+        })
+        this.articleInfo = res.data
+      } catch (error) {
+        console.log(error)
+      }
+    },
+  },
+}

+ 48 - 0
mixins/fileDetail.js

@@ -0,0 +1,48 @@
+import { mapGetters } from 'vuex'
+import downloadFile from '~/utils/donwload-tools'
+export default {
+  data() {
+    return {
+      fileId: '',
+      fileData: {},
+    }
+  },
+  computed: {
+    ...mapGetters(['routePrefix', 'supplierInfo', 'authUserId']),
+    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 this.$http.api.fetchFileDetail({
+          fileId: this.fileId,
+          authUserId: this.authUserId,
+        })
+        this.fileData = res.data
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 下载文件
+    onDownload(row, $event) {
+      console.log(row)
+      let downUrl = ''
+      if (row.packageType === 0) {
+        downUrl = `${process.env.BASE_URL}/wx/data/path/package/zip?fileId=${row.id}&fileName=${encodeURIComponent(row.fileName)}&authUserId=${this.authUserId}`
+      } else {
+        downUrl = `${process.env.BASE_URL}/download/file?ossName=${row.ossName}&fileName=${encodeURIComponent(row.fileName)}`
+      }
+      downloadFile(downUrl, row.fileName, this, $event)
+    },
+  },
+}

+ 130 - 0
mixins/fileList.js

@@ -0,0 +1,130 @@
+import { mapGetters } from 'vuex'
+import { toAuthorization } from '~/utils'
+import downloadFile from '~/utils/donwload-tools'
+import { isWeChat } from '~/utils/validator'
+export default {
+  layout: 'app-ross',
+  filters: {
+    crumbFormat(name) {
+      if (name.length < 12) return name
+      return name.substring(0, 10) + '…'
+    },
+    fileNameFormat(name) {
+      return name.replace(/(.+)(.txt$)/, (match, $1, $2) => {
+        return $1
+      })
+    },
+  },
+  data() {
+    return {
+      fileId: '',
+      list: [],
+      crumbList: [],
+    }
+  },
+  computed: {
+    ...mapGetters([
+      'routePrefix',
+      'supplierInfo',
+      'authUserId',
+      'accountType',
+      'appId',
+    ]),
+  },
+  created() {
+    this.fileId = this.$route.params.fileId
+    this.fetchFileList()
+    this.fetchCrumbsList()
+  },
+  methods: {
+    // 处理面包屑
+    generateCrumb(node) {
+      const list = []
+      if (!node) return list
+      function recursive(node) {
+        list.push(node)
+        if (node.childNode) {
+          recursive(node.childNode)
+        }
+      }
+      recursive(node)
+      return list
+    },
+    // 获取面包屑
+    async fetchCrumbsList() {
+      try {
+        const res = await this.$http.api.fetchCrumbsList({
+          authUserId: this.authUserId,
+          fileId: this.fileId,
+        })
+        this.crumbList = this.generateCrumb(res.data)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 下一级
+    onRowClick(row) {
+      if (row.fileType === 'article') {
+        if (!this.checkLogin()) return
+        if (row.articleType === 1) {
+          const url = `${this.routePrefix}/database/article-detail?id=${row.articleId}`
+          this.$router.push(url)
+        } else {
+          window.open(row.ossUrl, '_blank')
+        }
+      } else if (row.packageType === 1) {
+        if (!this.checkLogin()) return
+        this.$router.push(`${this.routePrefix}/docs/detail?id=${row.id}`)
+      } else {
+        this.$router.push(`${this.routePrefix}/docs/${row.id}`)
+      }
+    },
+    // 获取文件列表
+    async fetchFileList() {
+      try {
+        const res = await this.$http.api.fetchDocsList({
+          fileId: this.fileId,
+          authUserId: this.authUserId,
+        })
+        this.list = res.data
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 校验登录
+    checkLogin() {
+      const hasLogin = this.$store.getters.accessToken
+      if (!hasLogin) {
+        // 在微信浏览器中使用微信授权登录
+        if (isWeChat() && this.appId && this.accountType === 2) {
+          const payload = {
+            authUserId: this.authUserId,
+            routePrefix: this.routePrefix,
+          }
+          return toAuthorization(this.appId, payload)
+        }
+        this.$toast({ message: '请先登录', duration: 1000 })
+        this.$store.commit('app/SHOW_LOGIN')
+        return false
+      }
+      return true
+    },
+    // 下载文件
+    onDownload(row, $event) {
+      if (!this.checkLogin()) return
+      let downUrl = ''
+      if (row.packageType === 0) {
+        downUrl = `${process.env.BASE_URL}/wx/data/path/package/zip?fileId=${
+          row.id
+        }&fileName=${encodeURIComponent(row.fileName)}&authUserId=${
+          this.authUserId
+        }`
+      } else {
+        downUrl = `${process.env.BASE_URL}/download/file?ossName=${
+          row.ossName
+        }&fileName=${encodeURIComponent(row.fileName)}`
+      }
+      downloadFile(downUrl, row.fileName, this, $event)
+    },
+  },
+}

+ 9 - 7
nuxt.config.js

@@ -1,6 +1,8 @@
 import path from 'path'
 import fs from 'fs'
 
+console.log(process.env.BASE_URL)
+
 export default {
   router: {
     middleware: 'auth',
@@ -99,12 +101,12 @@ export default {
   server: {
     port: process.env.PORT,
     host: process.env.HOST,
-    // https:
-    //   process.env.HTTPS === 'true'
-    //     ? {
-    //         key: fs.readFileSync(path.join(__dirname, 'cert.key')),
-    //         cert: fs.readFileSync(path.join(__dirname, 'cert.crt')),
-    //       }
-    //     : null,
+    https:
+      process.env.HTTPS === 'true'
+        ? {
+            key: fs.readFileSync(path.join(__dirname, 'cert.key')),
+            cert: fs.readFileSync(path.join(__dirname, 'cert.crt')),
+          }
+        : null,
   },
 }

+ 328 - 0
pages/_template/app/docs/_fileId.vue

@@ -0,0 +1,328 @@
+<template>
+  <div class="page">
+    <div class="page-top flex flex-col justify-center items-center">
+      <span class="name mt-2" v-text="supplierInfo.shopName + '资料库'"></span>
+    </div>
+    <div class="page-content">
+      <!-- 面包屑 -->
+      <el-breadcrumb separator-class="el-icon-arrow-right">
+        <el-breadcrumb-item :to="{ path: `${routePrefix}/docs/0` }"
+          >全部文件</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: `${routePrefix}/docs/${item.id}` }"
+            >
+              <span>{{ item.fileName | crumbFormat }}</span>
+            </el-breadcrumb-item>
+          </template>
+        </template>
+      </el-breadcrumb>
+      <!-- 列表 -->
+      <div class="list-header">
+        <div class="row">
+          <div class="col">文件名</div>
+          <div class="col">时间</div>
+          <div class="col">大小</div>
+          <div class="col">操作</div>
+        </div>
+      </div>
+      <div class="list-body">
+        <div class="row" v-for="item in list" :key="item.id">
+          <div class="section pc">
+            <div class="col">
+              <doc-icon :type="item.fileType" :src="item.screenshot" />
+              <span
+                class="file-name"
+                @click="onRowClick(item)"
+                v-text="item.fileName"
+              ></span>
+            </div>
+            <div class="col">{{ item.saveTime | dateFormat }}</div>
+            <div class="col">
+              <span v-if="item.packageType > 0">{{
+                item.fileSize | fileSize
+              }}</span>
+              <span v-else>-</span>
+            </div>
+            <div class="col control">
+              <div class="download" @click="onDownload(item, $event)"></div>
+            </div>
+          </div>
+          <div class="section mobile">
+            <div class="col file-cover">
+              <doc-icon :type="item.fileType" :src="item.screenshot" />
+            </div>
+            <div class="col file-content" @click="onRowClick(item)">
+              <div class="file-name" v-if="item.fileType === 'article'">
+                {{ item.fileName | fileNameFormat }}
+              </div>
+              <div class="file-name" v-else>{{ item.fileName }}</div>
+              <div class="file-info">
+                <span class="date">{{ item.saveTime | dateFormat }}</span>
+                <span class="size">
+                  <span v-if="item.packageType > 0">{{
+                    item.fileSize | fileSize
+                  }}</span>
+                  <span v-else>-</span>
+                </span>
+              </div>
+            </div>
+            <div class="col control">
+              <div class="download" @click="onDownload(item, $event)"></div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import fileListMixin from '@/mixins/fileList'
+export default {
+  layout: 'app',
+  mixins: [fileListMixin],
+}
+</script>
+
+<style lang="scss" scoped>
+/* scss中可以用mixin来扩展 */
+@mixin ellipsis($line: 1) {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: $line;
+  -webkit-box-orient: vertical;
+}
+
+// pc 端
+@media screen and (min-width: 768px) {
+  .page {
+    margin-bottom: 16px;
+  }
+  .page-top {
+    height: 360px;
+    @include themify($themes) {
+      background: themed('pc-banner-doc');
+      background-size: auto 360px;
+    }
+    .logo {
+      display: block;
+      width: 120px;
+      height: 120px;
+      border-radius: 50%;
+      background: #fff;
+    }
+    .name {
+      font-size: 30px;
+      color: #fff;
+    }
+  }
+
+  .page-content {
+    width: 1200px;
+    margin: 0 auto;
+    background-color: #fff;
+    margin-top: 16px;
+    box-sizing: border-box;
+    overflow: hidden;
+    padding: 32px 0;
+
+    .el-breadcrumb {
+      margin: 0 24px 32px;
+    }
+
+    .list-header,
+    .list-body {
+      font-size: 14px;
+      color: #282828;
+      .col {
+        &:nth-child(1) {
+          flex: 1;
+        }
+        &:nth-child(2),
+        &:nth-child(3),
+        &:nth-child(4) {
+          width: 120px;
+          text-align: center;
+        }
+      }
+    }
+
+    .list-header {
+      .row {
+        display: flex;
+        justify-content: space-between;
+        padding: 0 24px 16px;
+      }
+    }
+
+    .list-body {
+      .row {
+        line-height: 70px;
+        color: #666666;
+        padding: 0 24px;
+        transition: all 0.4s;
+
+        &:hover {
+          background: #fff1f2;
+        }
+
+        .section {
+          display: flex;
+          justify-content: space-between;
+          border-top: 1px solid #f7f7f7;
+
+          &.mobile {
+            display: none;
+          }
+        }
+
+        img {
+          display: inline-block;
+          width: 32px;
+          height: 32px;
+          background: #eee;
+          margin-right: 24px;
+        }
+
+        .file-name {
+          cursor: pointer;
+          transition: all 0.4s;
+          margin-left: 24px;
+          &:hover {
+            color: #bc1724;
+          }
+        }
+
+        .control {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          .download {
+            display: block;
+            width: 24px;
+            height: 24px;
+            background: url(~assets/theme-images/common/pc-icon-download.png)
+              no-repeat center;
+            background-size: 24px;
+            margin: 0 auto;
+            cursor: pointer;
+
+            &:hover {
+              background-image: url(~assets/theme-images/common/pc-icon-download-hover.png);
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+// 移动 端
+@media screen and (max-width: 768px) {
+  .page-top {
+    height: 46vw;
+    @include themify($themes) {
+      background: themed('h5-banner-doc');
+      background-size: auto 46vw;
+    }
+    .logo {
+      display: block;
+      width: 14.8vw;
+      height: 14.8vw;
+      border-radius: 50%;
+      background: #fff;
+    }
+    .name {
+      font-size: 4vw;
+      color: #fff;
+    }
+  }
+
+  .page-content {
+    position: relative;
+    padding: 8vw 0;
+
+    .el-breadcrumb {
+      margin: 0 4vw 4vw;
+    }
+
+    .list-header {
+      display: none;
+    }
+
+    .list-body {
+      font-size: 3.6vw;
+      color: #282828;
+
+      .row {
+        color: #666666;
+        padding: 0 4vw;
+
+        .section {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          border-bottom: 0.1vw solid #f7f7f7;
+          height: 17.6vw;
+
+          &.pc {
+            display: none;
+          }
+
+          .file-cover {
+            img {
+              width: 8.8vw;
+              height: 8.8vw;
+              background: #eee;
+            }
+          }
+
+          .file-content {
+            flex: 1;
+            margin: 0 4.8vw;
+
+            .file-name {
+              font-size: 3.6vw;
+              @include ellipsis(1);
+            }
+
+            .file-info {
+              font-size: 3vw;
+              color: #999999;
+              margin-top: 1.6vw;
+
+              .size {
+                margin-left: 4vw;
+              }
+            }
+          }
+
+          .control {
+            width: 6.4vw;
+            height: 6.4vw;
+            .download {
+              display: block;
+              width: 6.4vw;
+              height: 6.4vw;
+              background: url(~assets/theme-images/common/h5-icon-download.png)
+                no-repeat center;
+              background-size: 6.4vw;
+              margin: 0 auto;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 83 - 0
pages/_template/app/docs/article-detail.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="page">
+    <div class="page-top">
+      <div class="title" v-text="articleInfo.articleTitle"></div>
+      <div class="date">{{ articleInfo.createTime | dateFormat }}</div>
+    </div>
+    <div class="page-content" v-html="html"></div>
+  </div>
+</template>
+
+<script>
+import articleDetailMixin from '@/mixins/articleDetail'
+export default {
+  layout: 'app',
+  mixins: [articleDetailMixin],
+}
+</script>
+
+<style scoped lang="scss">
+/* scss中可以用mixin来扩展 */
+@mixin ellipsis($line: 1) {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: $line;
+  -webkit-box-orient: vertical;
+}
+// pc 端
+@media screen and (min-width: 768px) {
+  .page {
+    width: 1200px;
+    margin: 24px auto;
+    box-sizing: border-box;
+    background: #fff;
+    padding: 24px;
+  }
+  .page-top {
+    padding-bottom: 24px;
+    border-bottom: 1px solid #d8d8d8;
+    .title {
+      font-size: 28px;
+      color: #101010;
+      line-height: 1.6;
+      text-align: justify;
+    }
+    .date {
+      font-size: 18px;
+      color: #b2b2b2;
+      margin-top: 24px;
+    }
+  }
+  .page-content {
+    padding-top: 24px;
+    color: #404040;
+  }
+}
+
+// 移动 端
+@media screen and (max-width: 768px) {
+  .page {
+    box-sizing: border-box;
+    background: #fff;
+    padding: 4vw;
+  }
+  .page-top {
+    .title {
+      font-size: 4.2vw;
+      color: #101010;
+      line-height: 1.6;
+      text-align: justify;
+    }
+    .date {
+      font-size: 3.2vw;
+      color: #b2b2b2;
+      margin-top: 4vw;
+    }
+  }
+  .page-content {
+    padding-top: 24px;
+    color: #404040;
+  }
+}
+</style>

+ 246 - 0
pages/_template/app/docs/detail.vue

@@ -0,0 +1,246 @@
+<template>
+  <div class="page-content">
+    <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"
+          />
+        </div>
+        <div class="content">
+          <div class="row">
+            <span class="date">时间:{{ fileData.saveTime | dateFormat }}</span>
+            <span class="size">大小:{{ fileData.fileSize | fileSize }}</span>
+          </div>
+        </div>
+      </div>
+      <div>
+        <div @click="onDownload(fileData, $event)" class="download"></div>
+      </div>
+    </div>
+    <div class="doc-content">
+      <img v-if="fileType === 'image'" :src="fileData.ossUrl" alt="" />
+      <video v-else-if="fileType === 'video'" :src="fileData.ossUrl" controls />
+      <div v-else class="other">
+        <doc-icon type="icon-other" :width="64" :height="64" />
+        <div class="name">{{ fileData.fileName }}</div>
+        <div class="download" @click="onDownload(fileData, $event)">
+          下载并打开
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import fileDetailMixin from '@/mixins/fileDetail'
+export default {
+  layout: 'app',
+  mixins: [fileDetailMixin],
+}
+</script>
+<style lang="scss" scoped>
+@media screen and (min-width: 768px) {
+  .page-content {
+    width: 1200px;
+    margin: 0 auto;
+    background: #fff;
+    margin-top: 16px;
+    padding: 24px;
+    box-sizing: border-box;
+  }
+  .doc-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    border-bottom: 1px solid #e8e8e8;
+    padding-bottom: 26px;
+
+    .download {
+      width: 24px;
+      height: 24px;
+      background: url(~assets/theme-images/common/pc-icon-download.png)
+        no-repeat center;
+      background-size: 24px;
+      cursor: pointer;
+
+      &:hover {
+        background-image: url(~assets/theme-images/common/pc-icon-download-hover.png);
+      }
+    }
+
+    .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 {
+    display: flex;
+    justify-content: center;
+    img,
+    video {
+      display: block;
+      max-height: 540px;
+      // background: #000;
+      margin-top: 40px;
+      margin-bottom: 24px;
+    }
+
+    .other {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      flex-direction: column;
+      height: 610px;
+
+      .name {
+        font-size: 16px;
+        color: #666666;
+        margin: 16px 0 24px;
+      }
+
+      .download {
+        width: 88px;
+        height: 36px;
+        background: #bc1724;
+        opacity: 1;
+        border-radius: 2px;
+        text-align: center;
+        line-height: 36px;
+        color: #ffffff;
+        font-size: 14px;
+        cursor: pointer;
+      }
+    }
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .page-content {
+    background: #fff;
+    box-sizing: border-box;
+    padding: 4vw;
+  }
+  .doc-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    border-bottom: 0.1vw solid #e8e8e8;
+    padding-bottom: 4vw;
+
+    .download {
+      width: 6.4vw;
+      height: 6.4vw;
+      background: url(~assets/theme-images/common/pc-icon-download.png)
+        no-repeat center;
+      background-size: 6.4vw;
+      cursor: pointer;
+
+      &:hover {
+        background-image: url(~assets/theme-images/common/pc-icon-download-hover.png);
+      }
+    }
+
+    .info {
+      display: flex;
+      align-items: center;
+
+      .cover {
+        img {
+          display: block;
+          width: 12vw;
+          height: 12vw;
+          background: skyblue;
+        }
+      }
+
+      .content {
+        margin-left: 4vw;
+        .name {
+          font-size: 3.6vw;
+          color: #282828;
+          margin-bottom: 1vw;
+        }
+        .date,
+        .size {
+          font-size: 3vw;
+          color: #999999;
+        }
+
+        .size {
+          margin-left: 4vw;
+        }
+      }
+    }
+  }
+
+  .doc-content {
+    display: flex;
+    justify-content: center;
+    img,
+    video {
+      display: block;
+      width: 100%;
+      // background: #000;
+      margin-top: 4vw;
+    }
+
+    .other {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      flex-direction: column;
+      height: 100vw;
+
+      .name {
+        font-size: 3.4vw;
+        color: #666666;
+        margin: 2.4vw 0 6.4vw;
+      }
+
+      .download {
+        width: 32vw;
+        height: 8.8vw;
+        background: #bc1724;
+        opacity: 1;
+        border-radius: 0.2vw;
+        text-align: center;
+        line-height: 8.8vw;
+        color: #ffffff;
+        font-size: 3.4vw;
+        cursor: pointer;
+      }
+    }
+  }
+}
+</style>

+ 2 - 2
pages/_template/app/index.vue

@@ -69,7 +69,7 @@ export default {
         {
           id: 2,
           name: '资料库',
-          path: '/database/article',
+          path: '/docs/0',
           active: false,
         },
         {
@@ -89,7 +89,7 @@ export default {
         'login_redicret',
         this.routePrefix + item.path
       )
-      if (item.id > 1 && !hasLogin) {
+      if (item.id > 2 && !hasLogin) {
         // 在微信浏览器中使用微信授权登录
         if (isWeChat() && this.appId && this.accountType === 2) {
           const payload = {

+ 335 - 0
pages/_template/ldm/docs/_fileId.vue

@@ -0,0 +1,335 @@
+<template>
+  <div class="page">
+    <div class="page-top flex flex-col justify-center items-center">
+      <img
+        class="logo"
+        src="https://static.caimei365.com/www/authentic/pc/ldm-logo-rect.png"
+      />
+      <div class="name mt-2">
+        Bring you into a new era<br />
+        of high-tech non-invasive beauty care
+      </div>
+    </div>
+    <div class="page-content">
+      <!-- 面包屑 -->
+      <el-breadcrumb separator-class="el-icon-arrow-right">
+        <el-breadcrumb-item :to="{ path: `${routePrefix}/docs/0` }"
+          >全部文件</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: `${routePrefix}/docs/${item.id}` }"
+            >
+              <span>{{ item.fileName | crumbFormat }}</span>
+            </el-breadcrumb-item>
+          </template>
+        </template>
+      </el-breadcrumb>
+      <!-- 列表 -->
+      <div class="list-header">
+        <div class="row">
+          <div class="col">文件名</div>
+          <div class="col">时间</div>
+          <div class="col">大小</div>
+          <div class="col">操作</div>
+        </div>
+      </div>
+      <div class="list-body">
+        <div class="row" v-for="item in list" :key="item.id">
+          <div class="section pc">
+            <div class="col">
+              <doc-icon :type="item.fileType" :src="item.screenshot" />
+              <span
+                class="file-name"
+                @click="onRowClick(item)"
+                v-text="item.fileName"
+              ></span>
+            </div>
+            <div class="col">{{ item.saveTime | dateFormat }}</div>
+            <div class="col">
+              <span v-if="item.packageType > 0">{{
+                item.fileSize | fileSize
+              }}</span>
+              <span v-else>-</span>
+            </div>
+            <div class="col control">
+              <div class="download" @click="onDownload(item, $event)"></div>
+            </div>
+          </div>
+          <div class="section mobile">
+            <div class="col file-cover">
+              <doc-icon :type="item.fileType" :src="item.screenshot" />
+            </div>
+            <div class="col file-content" @click="onRowClick(item)">
+              <div class="file-name" v-if="item.fileType === 'article'">
+                {{ item.fileName | fileNameFormat }}
+              </div>
+              <div class="file-name" v-else>{{ item.fileName }}</div>
+              <div class="file-info">
+                <span class="date">{{ item.saveTime | dateFormat }}</span>
+                <span class="size">
+                  <span v-if="item.packageType > 0">{{
+                    item.fileSize | fileSize
+                  }}</span>
+                  <span v-else>-</span>
+                </span>
+              </div>
+            </div>
+            <div class="col control">
+              <div class="download" @click="onDownload(item, $event)"></div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import fileListMixin from '@/mixins/fileList'
+export default {
+  layout: 'app-ldm',
+  mixins: [fileListMixin],
+}
+</script>
+
+<style lang="scss" scoped>
+/* scss中可以用mixin来扩展 */
+@mixin ellipsis($line: 1) {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: $line;
+  -webkit-box-orient: vertical;
+}
+
+// pc 端
+@media screen and (min-width: 768px) {
+  .page {
+    margin-bottom: 16px;
+  }
+  .page-top {
+    height: 466px;
+    .logo {
+      height: 61px;
+      width: 311px;
+      display: block;
+      background: #fff;
+    }
+
+    .name {
+      font-size: 19px;
+      color: #221815;
+      margin-top: 80px;
+      text-align: center;
+      line-height: 36px;
+      text-transform: uppercase;
+    }
+  }
+
+  .page-content {
+    width: 1000px;
+    margin: 0 auto;
+    background-color: #fff;
+    margin-top: 16px;
+    box-sizing: border-box;
+    overflow: hidden;
+    padding: 32px 0;
+
+    .el-breadcrumb {
+      margin: 0 24px 32px;
+    }
+
+    .list-header,
+    .list-body {
+      font-size: 14px;
+      color: #282828;
+      .col {
+        &:nth-child(1) {
+          flex: 1;
+        }
+        &:nth-child(2),
+        &:nth-child(3),
+        &:nth-child(4) {
+          width: 120px;
+          text-align: center;
+        }
+      }
+    }
+
+    .list-header {
+      .row {
+        display: flex;
+        justify-content: space-between;
+        padding: 0 24px 16px;
+      }
+    }
+
+    .list-body {
+      .row {
+        line-height: 70px;
+        color: #666666;
+        padding: 0 24px;
+        transition: all 0.4s;
+
+        &:hover {
+          background: #fff1f2;
+        }
+
+        .section {
+          display: flex;
+          justify-content: space-between;
+          border-top: 1px solid #f7f7f7;
+
+          &.mobile {
+            display: none;
+          }
+        }
+
+        img {
+          display: inline-block;
+          width: 32px;
+          height: 32px;
+          background: #eee;
+          margin-right: 24px;
+        }
+
+        .file-name {
+          cursor: pointer;
+          transition: all 0.4s;
+          margin-left: 24px;
+          &:hover {
+            color: #bc1724;
+          }
+        }
+
+        .control {
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          .download {
+            display: block;
+            width: 24px;
+            height: 24px;
+            background: url(~assets/theme-images/common/pc-icon-download.png)
+              no-repeat center;
+            background-size: 24px;
+            margin: 0 auto;
+            cursor: pointer;
+
+            &:hover {
+              background-image: url(~assets/theme-images/common/pc-icon-download-hover.png);
+            }
+          }
+        }
+      }
+    }
+  }
+}
+
+// 移动 端
+@media screen and (max-width: 768px) {
+  .page-top {
+    height: 76.4vw;
+    .logo {
+      height: 11.5vw;
+      width: 59vw;
+      display: block;
+      background: #fff;
+    }
+
+    .name {
+      font-size: 3.5vw;
+      color: #221815;
+      margin-top: 15vw;
+      text-align: center;
+      line-height: 6.6vw;
+      text-transform: uppercase;
+    }
+  }
+
+  .page-content {
+    position: relative;
+    padding: 8vw 0;
+
+    .el-breadcrumb {
+      margin: 0 4vw 4vw;
+    }
+
+    .list-header {
+      display: none;
+    }
+
+    .list-body {
+      font-size: 3.6vw;
+      color: #282828;
+
+      .row {
+        color: #666666;
+        padding: 0 4vw;
+
+        .section {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          border-bottom: 0.1vw solid #f7f7f7;
+          height: 17.6vw;
+
+          &.pc {
+            display: none;
+          }
+
+          .file-cover {
+            img {
+              width: 8.8vw;
+              height: 8.8vw;
+              background: #eee;
+            }
+          }
+
+          .file-content {
+            flex: 1;
+            margin: 0 4.8vw;
+
+            .file-name {
+              font-size: 3.6vw;
+              @include ellipsis(1);
+            }
+
+            .file-info {
+              font-size: 3vw;
+              color: #999999;
+              margin-top: 1.6vw;
+
+              .size {
+                margin-left: 4vw;
+              }
+            }
+          }
+
+          .control {
+            width: 6.4vw;
+            height: 6.4vw;
+            .download {
+              display: block;
+              width: 6.4vw;
+              height: 6.4vw;
+              background: url(~assets/theme-images/common/h5-icon-download.png)
+                no-repeat center;
+              background-size: 6.4vw;
+              margin: 0 auto;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 83 - 0
pages/_template/ldm/docs/article-detail.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="page">
+    <div class="page-top">
+      <div class="title" v-text="articleInfo.articleTitle"></div>
+      <div class="date">{{ articleInfo.createTime | dateFormat }}</div>
+    </div>
+    <div class="page-content" v-html="html"></div>
+  </div>
+</template>
+
+<script>
+import articleDetailMixin from '@/mixins/articleDetail'
+export default {
+  layout: 'app-ldm',
+  mixins: [articleDetailMixin],
+}
+</script>
+
+<style scoped lang="scss">
+/* scss中可以用mixin来扩展 */
+@mixin ellipsis($line: 1) {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: $line;
+  -webkit-box-orient: vertical;
+}
+// pc 端
+@media screen and (min-width: 768px) {
+  .page {
+    width: 1000px;
+    margin: 24px auto;
+    box-sizing: border-box;
+    background: #fff;
+    padding: 24px;
+  }
+  .page-top {
+    padding-bottom: 24px;
+    border-bottom: 1px solid #d8d8d8;
+    .title {
+      font-size: 28px;
+      color: #101010;
+      line-height: 1.6;
+      text-align: justify;
+    }
+    .date {
+      font-size: 18px;
+      color: #b2b2b2;
+      margin-top: 24px;
+    }
+  }
+  .page-content {
+    padding-top: 24px;
+    color: #404040;
+  }
+}
+
+// 移动 端
+@media screen and (max-width: 768px) {
+  .page {
+    box-sizing: border-box;
+    background: #fff;
+    padding: 4vw;
+  }
+  .page-top {
+    .title {
+      font-size: 4.2vw;
+      color: #101010;
+      line-height: 1.6;
+      text-align: justify;
+    }
+    .date {
+      font-size: 3.2vw;
+      color: #b2b2b2;
+      margin-top: 4vw;
+    }
+  }
+  .page-content {
+    padding-top: 24px;
+    color: #404040;
+  }
+}
+</style>

+ 247 - 0
pages/_template/ldm/docs/detail.vue

@@ -0,0 +1,247 @@
+<template>
+  <div class="page-content">
+    <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"
+          />
+        </div>
+        <div class="content">
+          <div class="name">{{ fileData.fileName }}</div>
+          <div class="row">
+            <span class="date">时间:{{ fileData.saveTime | dateFormat }}</span>
+            <span class="size">大小:{{ fileData.fileSize | fileSize }}</span>
+          </div>
+        </div>
+      </div>
+      <div>
+        <div @click="onDownload(fileData, $event)" class="download"></div>
+      </div>
+    </div>
+    <div class="doc-content">
+      <img v-if="fileType === 'image'" :src="fileData.ossUrl" alt="" />
+      <video v-else-if="fileType === 'video'" :src="fileData.ossUrl" controls />
+      <div v-else class="other">
+        <doc-icon type="icon-other" :width="64" :height="64" />
+        <div class="name">{{ fileData.fileName }}</div>
+        <div class="download" @click="onDownload(fileData, $event)">
+          下载并打开
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import fileDetailMixin from '@/mixins/fileDetail'
+export default {
+  layout: 'app-ldm',
+  mixins: [fileDetailMixin],
+}
+</script>
+<style lang="scss" scoped>
+@media screen and (min-width: 768px) {
+  .page-content {
+    width: 1000px;
+    margin: 0 auto;
+    background: #fff;
+    margin-top: 16px;
+    padding: 24px;
+    box-sizing: border-box;
+  }
+  .doc-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    border-bottom: 1px solid #e8e8e8;
+    padding-bottom: 26px;
+
+    .download {
+      width: 24px;
+      height: 24px;
+      background: url(~assets/theme-images/common/pc-icon-download.png)
+        no-repeat center;
+      background-size: 24px;
+      cursor: pointer;
+
+      &:hover {
+        background-image: url(~assets/theme-images/common/pc-icon-download-hover.png);
+      }
+    }
+
+    .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 {
+    display: flex;
+    justify-content: center;
+    img,
+    video {
+      display: block;
+      max-height: 540px;
+      // background: #000;
+      margin-top: 40px;
+      margin-bottom: 24px;
+    }
+
+    .other {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      flex-direction: column;
+      height: 610px;
+
+      .name {
+        font-size: 16px;
+        color: #666666;
+        margin: 16px 0 24px;
+      }
+
+      .download {
+        width: 88px;
+        height: 36px;
+        background: #bc1724;
+        opacity: 1;
+        border-radius: 2px;
+        text-align: center;
+        line-height: 36px;
+        color: #ffffff;
+        font-size: 14px;
+        cursor: pointer;
+      }
+    }
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .page-content {
+    background: #fff;
+    box-sizing: border-box;
+    padding: 4vw;
+  }
+  .doc-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    border-bottom: 0.1vw solid #e8e8e8;
+    padding-bottom: 4vw;
+
+    .download {
+      width: 6.4vw;
+      height: 6.4vw;
+      background: url(~assets/theme-images/common/pc-icon-download.png)
+        no-repeat center;
+      background-size: 6.4vw;
+      cursor: pointer;
+
+      &:hover {
+        background-image: url(~assets/theme-images/common/pc-icon-download-hover.png);
+      }
+    }
+
+    .info {
+      display: flex;
+      align-items: center;
+
+      .cover {
+        img {
+          display: block;
+          width: 12vw;
+          height: 12vw;
+          background: skyblue;
+        }
+      }
+
+      .content {
+        margin-left: 4vw;
+        .name {
+          font-size: 3.6vw;
+          color: #282828;
+          margin-bottom: 1vw;
+        }
+        .date,
+        .size {
+          font-size: 3vw;
+          color: #999999;
+        }
+
+        .size {
+          margin-left: 4vw;
+        }
+      }
+    }
+  }
+
+  .doc-content {
+    display: flex;
+    justify-content: center;
+    img,
+    video {
+      display: block;
+      width: 100%;
+      // background: #000;
+      margin-top: 4vw;
+    }
+
+    .other {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      flex-direction: column;
+      height: 100vw;
+
+      .name {
+        font-size: 3.4vw;
+        color: #666666;
+        margin: 2.4vw 0 6.4vw;
+      }
+
+      .download {
+        width: 32vw;
+        height: 8.8vw;
+        background: #bc1724;
+        opacity: 1;
+        border-radius: 0.2vw;
+        text-align: center;
+        line-height: 8.8vw;
+        color: #ffffff;
+        font-size: 3.4vw;
+        cursor: pointer;
+      }
+    }
+  }
+}
+</style>

+ 2 - 2
pages/_template/ldm/index.vue

@@ -55,7 +55,7 @@ export default {
           id: 1,
           name: '官方产品资料',
           image: 'ldm-icon-database.png',
-          path: '/database/package',
+          path: '/docs/0',
         },
       ],
     }
@@ -78,7 +78,7 @@ export default {
         this.routePrefix + item.path
       )
       const hasLogin = this.$store.getters.accessToken
-      if (item.id > 0 && !hasLogin) {
+      if (item.id > 1 && !hasLogin) {
         // 在微信浏览器中使用微信授权登录
         if (isWeChat() && this.appId && this.accountType === 2) {
           const payload = {

+ 53 - 63
pages/_template/ross/docs/_fileId.vue

@@ -5,10 +5,25 @@
     </div>
     <div class="page-content">
       <!-- 面包屑 -->
-      <el-breadcrumb separator=">">
-        <el-breadcrumb-item>首页</el-breadcrumb-item>
-        <el-breadcrumb-item>活动管理</el-breadcrumb-item>
-        <el-breadcrumb-item>活动管理</el-breadcrumb-item>
+      <el-breadcrumb separator-class="el-icon-arrow-right">
+        <el-breadcrumb-item :to="{ path: `${routePrefix}/docs/0` }"
+          >全部文件</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: `${routePrefix}/docs/${item.id}` }"
+            >
+              <span>{{ item.fileName | crumbFormat }}</span>
+            </el-breadcrumb-item>
+          </template>
+        </template>
       </el-breadcrumb>
       <!-- 列表 -->
       <div class="list-header">
@@ -23,54 +38,45 @@
         <div class="row" v-for="item in list" :key="item.id">
           <div class="section pc">
             <div class="col">
-              <img src="https://picsum.photos/32/32" alt="" />
-              <span
-                class="file-name"
-                @click="onRowClick"
-                v-text="item.fileName"
-              ></span>
+              <doc-icon :type="item.fileType" :src="item.screenshot" />
+              <span class="file-name" v-if="item.fileType === 'article'" @click="onRowClick(item)">
+                {{ item.fileName | fileNameFormat }}
+              </span>
+              <span class="file-name" v-else @click="onRowClick(item)">{{ item.fileName }}</span>
             </div>
             <div class="col">{{ item.saveTime | dateFormat }}</div>
-            <div class="col">{{ item.fileSize | fileSize }}</div>
-            <div class="col control"><div class="download"></div></div>
-          </div>
-          <div class="section mobile">
-            <div class="col file-cover">
-              <img src="https://picsum.photos/32/32" alt="" />
-            </div>
-            <div class="col file-content">
-              <div class="file-name" v-text="item.fileName"></div>
-              <div class="file-info">
-                <span class="date">{{ item.saveTime | dateFormat }}</span>
-                <span class="size">{{ item.fileSize | fileSize }}</span>
-              </div>
-            </div>
-            <div class="col control"><div class="download"></div></div>
-          </div>
-        </div>
-
-        <div class="row">
-          <div class="section pc">
             <div class="col">
-              <img src="https://picsum.photos/32/32" alt="" />
-              <span class="file-name" @click="onRowClick">图像检测分析</span>
+              <span v-if="item.packageType > 0">{{
+                item.fileSize | fileSize
+              }}</span>
+              <span v-else>-</span>
+            </div>
+            <div class="col control">
+              <div class="download" @click="onDownload(item, $event)"></div>
             </div>
-            <div class="col">2022-08-02</div>
-            <div class="col">25MB</div>
-            <div class="col control"><div class="download"></div></div>
           </div>
           <div class="section mobile">
             <div class="col file-cover">
-              <img src="https://picsum.photos/32/32" alt="" />
+              <doc-icon :type="item.fileType" :src="item.screenshot" />
             </div>
-            <div class="col file-content">
-              <div class="file-name">图像检测分析</div>
+            <div class="col file-content" @click="onRowClick(item)">
+              <div class="file-name" v-if="item.fileType === 'article'">
+                {{ item.fileName | fileNameFormat }}
+              </div>
+              <div class="file-name" v-else>{{ item.fileName }}</div>
               <div class="file-info">
-                <span class="date">2022-08-02</span>
-                <span class="size">25MB</span>
+                <span class="date">{{ item.saveTime | dateFormat }}</span>
+                <span class="size">
+                  <span v-if="item.packageType > 0">{{
+                    item.fileSize | fileSize
+                  }}</span>
+                  <span v-else>-</span>
+                </span>
               </div>
             </div>
-            <div class="col control"><div class="download"></div></div>
+            <div class="col control">
+              <div class="download" @click="onDownload(item, $event)"></div>
+            </div>
           </div>
         </div>
       </div>
@@ -79,30 +85,10 @@
 </template>
 
 <script>
-import { mapGetters } from 'vuex'
+import fileListMixin from '@/mixins/fileList'
 export default {
   layout: 'app-ross',
-  filters: {},
-  asyncData({ $http, params }) {
-    return $http.api.fetchDocsList({ fileId: params.fileId }).then((res) => {
-      return {
-        list: res.data || [],
-      }
-    })
-  },
-  data() {
-    return {
-      list: [],
-    }
-  },
-  computed: {
-    ...mapGetters(['routePrefix', 'supplierInfo', 'authUserId']),
-  },
-  methods: {
-    onRowClick() {
-      this.$router.push(`${this.routePrefix}/docs/${11}`)
-    },
-  },
+  mixins: [fileListMixin],
 }
 </script>
 
@@ -118,6 +104,9 @@ export default {
 
 // pc 端
 @media screen and (min-width: 768px) {
+  .page {
+    margin-bottom: 16px;
+  }
   .page-top {
     height: 360px;
     @include themify($themes) {
@@ -207,6 +196,7 @@ export default {
         .file-name {
           cursor: pointer;
           transition: all 0.4s;
+          margin-left: 24px;
           &:hover {
             color: #f3920d;
           }

+ 83 - 0
pages/_template/ross/docs/article-detail.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="page">
+    <div class="page-top">
+      <div class="title" v-text="articleInfo.articleTitle"></div>
+      <div class="date">{{ articleInfo.createTime | dateFormat }}</div>
+    </div>
+    <div class="page-content" v-html="html"></div>
+  </div>
+</template>
+
+<script>
+import articleDetailMixin from '@/mixins/articleDetail'
+export default {
+  layout: 'app-ross',
+  mixins: [articleDetailMixin],
+}
+</script>
+
+<style scoped lang="scss">
+/* scss中可以用mixin来扩展 */
+@mixin ellipsis($line: 1) {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: $line;
+  -webkit-box-orient: vertical;
+}
+// pc 端
+@media screen and (min-width: 768px) {
+  .page {
+    width: 1200px;
+    margin: 24px auto;
+    box-sizing: border-box;
+    background: #fff;
+    padding: 24px;
+  }
+  .page-top {
+    padding-bottom: 24px;
+    border-bottom: 1px solid #d8d8d8;
+    .title {
+      font-size: 28px;
+      color: #101010;
+      line-height: 1.6;
+      text-align: justify;
+    }
+    .date {
+      font-size: 18px;
+      color: #b2b2b2;
+      margin-top: 24px;
+    }
+  }
+  .page-content {
+    padding-top: 24px;
+    color: #404040;
+  }
+}
+
+// 移动 端
+@media screen and (max-width: 768px) {
+  .page {
+    box-sizing: border-box;
+    background: #fff;
+    padding: 4vw;
+  }
+  .page-top {
+    .title {
+      font-size: 4.2vw;
+      color: #101010;
+      line-height: 1.6;
+      text-align: justify;
+    }
+    .date {
+      font-size: 3.2vw;
+      color: #b2b2b2;
+      margin-top: 4vw;
+    }
+  }
+  .page-content {
+    padding-top: 24px;
+    color: #404040;
+  }
+}
+</style>

+ 242 - 0
pages/_template/ross/docs/detail.vue

@@ -0,0 +1,242 @@
+<template>
+  <div class="page-content">
+    <div v-if="fileData" class="doc-header">
+      <div class="info">
+        <div class="cover">
+          <doc-icon :type="fileData.fileType" :width="32" :height="32" />
+        </div>
+        <div class="content">
+          <div class="name">{{ fileData.fileName }}</div>
+          <div class="row">
+            <span class="date">时间:{{ fileData.saveTime | dateFormat }}</span>
+            <span class="size">大小:{{ fileData.fileSize | fileSize }}</span>
+          </div>
+        </div>
+      </div>
+      <div>
+        <div @click="onDownload(fileData, $event)" class="download"></div>
+      </div>
+    </div>
+    <div class="doc-content">
+      <img v-if="fileType === 'image'" :src="fileData.ossUrl" alt="" />
+      <video v-else-if="fileType === 'video'" :src="fileData.ossUrl" controls />
+      <div v-else class="other">
+        <doc-icon type="icon-other" :width="64" :height="64" />
+        <div class="name">{{ fileData.fileName }}</div>
+        <div class="download" @click="onDownload(fileData, $event)">
+          下载并打开
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+<script>
+import fileDetailMixin from '@/mixins/fileDetail'
+export default {
+  layout: 'app-ross',
+  mixins: [fileDetailMixin],
+}
+</script>
+<style lang="scss" scoped>
+@media screen and (min-width: 768px) {
+  .page-content {
+    width: 1200px;
+    margin: 0 auto;
+    background: #fff;
+    margin-top: 16px;
+    padding: 24px;
+    box-sizing: border-box;
+  }
+  .doc-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    border-bottom: 1px solid #e8e8e8;
+    padding-bottom: 26px;
+
+    .download {
+      width: 24px;
+      height: 24px;
+      background: url(~assets/theme-images/common/pc-icon-download.png)
+        no-repeat center;
+      background-size: 24px;
+      cursor: pointer;
+
+      &:hover {
+        background-image: url(~assets/theme-images/common/pc-icon-download-hover.png);
+      }
+    }
+
+    .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 {
+    display: flex;
+    justify-content: center;
+    img,
+    video {
+      display: block;
+      max-height: 540px;
+      // background: #000;
+      margin-top: 40px;
+      margin-bottom: 24px;
+    }
+
+    .other {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      flex-direction: column;
+      height: 610px;
+
+      .name {
+        font-size: 16px;
+        color: #666666;
+        margin: 16px 0 24px;
+      }
+
+      .download {
+        width: 88px;
+        height: 36px;
+        background: #f3920d;
+        opacity: 1;
+        border-radius: 2px;
+        text-align: center;
+        line-height: 36px;
+        color: #ffffff;
+        font-size: 14px;
+        cursor: pointer;
+      }
+    }
+  }
+}
+
+@media screen and (max-width: 768px) {
+  .page-content {
+    background: #fff;
+    box-sizing: border-box;
+    padding: 4vw;
+  }
+  .doc-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    border-bottom: 0.1vw solid #e8e8e8;
+    padding-bottom: 4vw;
+
+    .download {
+      width: 6.4vw;
+      height: 6.4vw;
+      background: url(~assets/theme-images/common/pc-icon-download.png)
+        no-repeat center;
+      background-size: 6.4vw;
+      cursor: pointer;
+
+      &:hover {
+        background-image: url(~assets/theme-images/common/pc-icon-download-hover.png);
+      }
+    }
+
+    .info {
+      display: flex;
+      align-items: center;
+
+      .cover {
+        img {
+          display: block;
+          width: 12vw;
+          height: 12vw;
+          background: skyblue;
+        }
+      }
+
+      .content {
+        margin-left: 4vw;
+        .name {
+          font-size: 3.6vw;
+          color: #282828;
+          margin-bottom: 1vw;
+        }
+        .date,
+        .size {
+          font-size: 3vw;
+          color: #999999;
+        }
+
+        .size {
+          margin-left: 4vw;
+        }
+      }
+    }
+  }
+
+  .doc-content {
+    display: flex;
+    justify-content: center;
+    img,
+    video {
+      display: block;
+      width: 100%;
+      // background: #000;
+      margin-top: 4vw;
+    }
+
+    .other {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      flex-direction: column;
+      height: 100vw;
+
+      .name {
+        font-size: 3.4vw;
+        color: #666666;
+        margin: 2.4vw 0 6.4vw;
+      }
+
+      .download {
+        width: 32vw;
+        height: 8.8vw;
+        background: #f3920d;
+        opacity: 1;
+        border-radius: 0.2vw;
+        text-align: center;
+        line-height: 8.8vw;
+        color: #ffffff;
+        font-size: 3.4vw;
+        cursor: pointer;
+      }
+    }
+  }
+}
+</style>

+ 33 - 1
utils/donwload-tools.js

@@ -1,6 +1,8 @@
 import SimpleProgress from '@/components/SimpleProgress'
 import handleClipboard from '@/utils/clipboard'
 import { isWeChat } from '@/utils/validator'
+import Vue from 'vue'
+import { Dialog } from 'vant'
 
 let uuid = 0 // 进度条id
 
@@ -29,7 +31,36 @@ export async function downloadWithUrl(downUrl, fileName, self) {
   })
 
   try {
-    const data = await fetch(downUrl)
+    const data = await fetch(downUrl, {
+      headers: { 'X-Token': self.$store.getters.accessToken },
+    })
+    if (data.redirected) {
+      const resultData = await data.json()
+      console.log(resultData.code)
+      // 登录过期
+      if (resultData.code === -99) {
+        notification && notification.close()
+        const result = await Dialog.alert({
+          title: '提示',
+          message: '登录已过期,请重新登录',
+          theme: 'round-button',
+          confirmButtonColor: 'linear-gradient(to left, #404040, #101010)',
+        })
+
+        Vue.prototype.$removeStorage(
+          self.$store.getters.routePrefix,
+          'userInfo'
+        )
+        self.$store.dispatch('user/logout')
+
+        if (result === 'confirm') {
+          // redirect(self.$store.getters.routePrefix)
+          self.$router.replace(self.$store.getters.routePrefix)
+        }
+        return
+      }
+    }
+
     const res = await data.blob()
     const link = document.createElement('a')
     const url = URL.createObjectURL(res)
@@ -40,6 +71,7 @@ export async function downloadWithUrl(downUrl, fileName, self) {
     URL.revokeObjectURL(url)
   } catch (err) {
     self.$message.error(`下载${fileName}失败`)
+    console.log(err)
   } finally {
     clearInterval(tiemer)
     progressRef.percentage = 100