Pārlūkot izejas kodu

mp:素材管理,增加【图片】

YunaiV 2 gadi atpakaļ
vecāks
revīzija
5a1d02dffd

+ 4 - 2
yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java

@@ -32,8 +32,10 @@ public interface ErrorCodeConstants {
     ErrorCode USER_UPDATE_TAG_FAIL = new ErrorCode(1006003001, "更新用户标签失败,原因:{}");
 
     // ========== 公众号素材 1006004000============
-    ErrorCode MATERIAL_UPLOAD_FAIL = new ErrorCode(1006004000, "上传素材失败,原因:{}");
-    ErrorCode MATERIAL_IMAGE_UPLOAD_FAIL = new ErrorCode(1006004000, "上传图片失败,原因:{}");
+    ErrorCode MATERIAL_NOT_EXISTS = new ErrorCode(1006004000, "素材不存在");
+    ErrorCode MATERIAL_UPLOAD_FAIL = new ErrorCode(1006004001, "上传素材失败,原因:{}");
+    ErrorCode MATERIAL_IMAGE_UPLOAD_FAIL = new ErrorCode(1006004002, "上传图片失败,原因:{}");
+    ErrorCode MATERIAL_DELETE_FAIL = new ErrorCode(1006004003, "删除素材失败,原因:{}");
 
     // ========== 公众号消息 1006005000============
     ErrorCode MESSAGE_SEND_FAIL = new ErrorCode(1006005000, "发送消息失败,原因:{}");

+ 16 - 7
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java

@@ -7,12 +7,11 @@ import cn.iocoder.yudao.module.mp.convert.material.MpMaterialConvert;
 import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
 import cn.iocoder.yudao.module.mp.service.material.MpMaterialService;
 import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
 import io.swagger.annotations.ApiOperation;
+import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import javax.validation.Valid;
@@ -20,8 +19,6 @@ import java.io.IOException;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
-// TODO @芋艿:权限
-
 @Api(tags = "管理后台 - 公众号素材")
 @RestController
 @RequestMapping("/mp/material")
@@ -33,6 +30,7 @@ public class MpMaterialController {
 
     @ApiOperation("上传临时素材")
     @PostMapping("/upload-temporary")
+    @PreAuthorize("@ss.hasPermission('mp:material:upload-temporary')")
     public CommonResult<MpMaterialUploadRespVO> uploadTemporaryMaterial(
             @Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException {
         MpMaterialDO material = mpMaterialService.uploadTemporaryMaterial(reqVO);
@@ -41,14 +39,25 @@ public class MpMaterialController {
 
     @ApiOperation("上传永久素材")
     @PostMapping("/upload-permanent")
+    @PreAuthorize("@ss.hasPermission('mp:material:upload-permanent')")
     public CommonResult<MpMaterialUploadRespVO> uploadPermanentMaterial(
             @Valid MpMaterialUploadPermanentReqVO reqVO) throws IOException {
         MpMaterialDO material = mpMaterialService.uploadPermanentMaterial(reqVO);
         return success(MpMaterialConvert.INSTANCE.convert(material));
     }
 
+    @ApiOperation("删除素材")
+    @DeleteMapping("/delete-permanent")
+    @ApiImplicitParam(name = "id", value = "编号", required = true, example = "1024", dataTypeClass = Long.class)
+    @PreAuthorize("@ss.hasPermission('mp:material:delete')")
+    public CommonResult<Boolean> deleteMaterial(@RequestParam("id") Long id) {
+        mpMaterialService.deleteMaterial(id);
+        return success(true);
+    }
+
     @ApiOperation("上传图文内容中的图片")
     @PostMapping("/upload-news-image")
+    @PreAuthorize("@ss.hasPermission('mp:material:upload-news-image')")
     public CommonResult<String> uploadNewsImage(@Valid MpMaterialUploadNewsImageReqVO reqVO)
             throws IOException {
         return success(mpMaterialService.uploadNewsImage(reqVO));
@@ -56,10 +65,10 @@ public class MpMaterialController {
 
     @ApiOperation("获得素材分页")
     @GetMapping("/page")
+    @PreAuthorize("@ss.hasPermission('mp:material:query')")
     public CommonResult<PageResult<MpMaterialRespVO>> getMaterialPage(@Valid MpMaterialPageReqVO pageReqVO) {
         PageResult<MpMaterialDO> pageResult = mpMaterialService.getMaterialPage(pageReqVO);
         return success(MpMaterialConvert.INSTANCE.convertPage(pageResult));
     }
 
-
 }

+ 2 - 1
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java

@@ -22,7 +22,8 @@ public interface MpMaterialMapper extends BaseMapperX<MpMaterialDO> {
         return selectPage(pageReqVO, new LambdaQueryWrapperX<MpMaterialDO>()
                 .eq(MpMaterialDO::getAccountId, pageReqVO.getAccountId())
                 .eqIfPresent(MpMaterialDO::getPermanent, pageReqVO.getPermanent())
-                .eqIfPresent(MpMaterialDO::getType, pageReqVO.getType()));
+                .eqIfPresent(MpMaterialDO::getType, pageReqVO.getType())
+                .orderByDesc(MpMaterialDO::getId));
     }
 
     default List<MpMaterialDO> selectListByMediaId(Collection<String> mediaIds) {

+ 7 - 0
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java

@@ -74,4 +74,11 @@ public interface MpMaterialService {
      */
     List<MpMaterialDO> getMaterialListByMediaId(Collection<String> mediaIds);
 
+    /**
+     * 删除素材
+     *
+     * @param id 编号
+     */
+    void deleteMaterial(Long id);
+
 }

+ 22 - 2
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java

@@ -32,8 +32,7 @@ import java.util.Collection;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MATERIAL_IMAGE_UPLOAD_FAIL;
-import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MATERIAL_UPLOAD_FAIL;
+import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.*;
 
 /**
  * 公众号素材 Service 接口
@@ -172,6 +171,27 @@ public class MpMaterialServiceImpl implements MpMaterialService {
         return mpMaterialMapper.selectListByMediaId(mediaIds);
     }
 
+    @Override
+    public void deleteMaterial(Long id) {
+        MpMaterialDO material = mpMaterialMapper.selectById(id);
+        if (material == null) {
+            throw exception(MATERIAL_NOT_EXISTS);
+        }
+
+        // 第一步,从公众号删除
+        if (material.getPermanent()) {
+            WxMpService mpService = mpServiceFactory.getRequiredMpService(material.getAppId());
+            try {
+                mpService.getMaterialService().materialDelete(material.getMediaId());
+            } catch (WxErrorException e) {
+                throw exception(MATERIAL_DELETE_FAIL, e.getError().getErrorMsg());
+            }
+        }
+
+        // 第二步,从数据库中删除
+        mpMaterialMapper.deleteById(id);
+    }
+
     /**
      * 下载微信媒体文件的内容,并上传到文件服务
      *

+ 8 - 0
yudao-ui-admin/src/api/mp/material.js

@@ -8,3 +8,11 @@ export function getMaterialPage(query) {
     params: query
   })
 }
+
+// 删除公众号永久素材
+export function deletePermanentMaterial(id) {
+  return request({
+    url: '/mp/material/delete-permanent?id=' + id,
+    method: 'delete'
+  })
+}

+ 0 - 6
yudao-ui-admin/src/views/mp/components/wx-material-select/main.vue

@@ -19,9 +19,6 @@
       </div>
     </div>
     <!-- 分页组件 -->
-    <div v-if="list.length <= 0 && !loading" class="el-table__empty-block">
-      <span class="el-table__empty-text">暂无数据</span>
-    </div>
     <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
                 @pagination="getMaterialPage"/>
   </div>
@@ -92,9 +89,6 @@
       </div>
     </div>
     <!-- 分页组件 -->
-    <div v-if="list.length <= 0 && !loading" class="el-table__empty-block">
-      <span class="el-table__empty-text">暂无数据</span>
-    </div>
     <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
                 @pagination="getMaterialPage"/>
   </div>

+ 2 - 2
yudao-ui-admin/src/views/mp/components/wx-reply/main.vue

@@ -235,7 +235,7 @@
           "title":'',
           "introduction":''
         },
-        actionUrl: process.env.VUE_APP_BASE_API +'/admin-api/mp/material/upload-temporary',
+        actionUrl: process.env.VUE_APP_BASE_API + '/admin-api/mp/material/upload-temporary',
         headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
       }
     },
@@ -279,7 +279,7 @@
         this.uploadData.accountId = this.objData.accountId;
         return true;
       },
-      beforeImageUpload(file){
+      beforeImageUpload(file) {
         // 校验格式
         const isType = file.type === 'image/jpeg'
             || file.type === 'image/png'

+ 1 - 5
yudao-ui-admin/src/views/mp/draft/index.vue

@@ -64,9 +64,6 @@ SOFTWARE.
       </div>
     </div>
     <!-- 分页记录 -->
-    <div v-if="list.length <= 0 && !loading" class="el-table__empty-block">
-      <span class="el-table__empty-text">暂无数据</span>
-    </div>
     <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
                 @pagination="getList"/>
 
@@ -218,7 +215,6 @@ export default {
       this.accounts = response.data;
       // 默认选中第一个
       if (this.accounts.length > 0) {
-        this.queryParams.accountId = this.accounts[0].id;
         this.setAccountId(this.accounts[0].id);
       }
       // 加载数据
@@ -241,7 +237,7 @@ export default {
       }
 
       this.loading = true
-      getDraftPage((this.queryParams)).then(response => {
+      getDraftPage(this.queryParams).then(response => {
         // 将 thumbUrl 转成 picUrl,保证 wx-news 组件可以预览封面
         response.data.list.forEach(item => {
           const newsItem = item.content.newsItem;

+ 0 - 3
yudao-ui-admin/src/views/mp/freePublish/index.vue

@@ -51,9 +51,6 @@ SOFTWARE.
       </div>
     </div>
     <!-- 分页组件 -->
-    <div v-if="list.length <=0 && !loading" class="el-table__empty-block">
-      <span class="el-table__empty-text">暂无数据</span>
-    </div>
     <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
                 @pagination="getList"/>
   </div>

+ 502 - 0
yudao-ui-admin/src/views/mp/material/index.vue

@@ -0,0 +1,502 @@
+<!--
+MIT License
+
+Copyright (c) 2020 www.joolun.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+-->
+<template>
+  <div class="app-container">
+    <!-- 搜索工作栏 -->
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+      <el-form-item label="公众号" prop="accountId">
+        <el-select v-model="queryParams.accountId" placeholder="请选择公众号">
+          <el-option v-for="item in accounts" :key="parseInt(item.id)" :label="item.name" :value="parseInt(item.id)" />
+        </el-select>
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-tabs v-model="type" @tab-click="handleClick">
+      <!-- tab 1:图片  -->
+      <el-tab-pane name="image">
+        <span slot="label"><i class="el-icon-picture"></i> 图片</span>
+        <div class="add_but" v-hasPermi="['mp:material:upload-permanent']">
+          <el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
+                     :before-upload="beforeImageUpload" :on-success="handleUploadSuccess">
+            <el-button size="mini" type="primary">点击上传</el-button>
+            <sapn slot="tip" class="el-upload__tip" style="margin-left: 5px">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</sapn>
+          </el-upload>
+        </div>
+        <div class="waterfall" v-loading="loading">
+          <div class="waterfall-item" v-for="item in list" :key='item.id'>
+            <a target="_blank" :href="item.url">
+              <img class="material-img" :src="item.url">
+              <div class="item-name">{{item.name}}</div>
+            </a>
+            <el-row class="ope-row">
+              <el-button type="danger" icon="el-icon-delete" circle @click="handleDelete(item)"
+                         v-hasPermi="['mp:material:delete']"/>
+            </el-row>
+          </div>
+        </div>
+        <!-- 分页组件 -->
+        <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
+                    @pagination="getList"/>
+      </el-tab-pane>
+      <el-tab-pane name="voice">
+        <span slot="label"><i class="el-icon-microphone"></i> 语音</span>
+        <div class="add_but">
+          <el-upload
+                  :action="actionUrl"
+                  :headers="headers"
+                  multiple
+                  :limit="1"
+                  :on-success="handleUploadSuccess"
+                  :file-list="fileList"
+                  :before-upload="beforeVoiceUpload"
+                  :data="uploadData">
+            <el-button size="mini" type="primary">点击上传</el-button>
+            <div slot="tip" class="el-upload__tip">
+              格式支持mp3/wma/wav/amr,文件大小不超过2M,播放长度不超过60s
+            </div>
+          </el-upload>
+        </div>
+        <el-table
+                :data="list"
+                stripe
+                border
+                v-loading="loading">
+          <el-table-column
+                  prop="mediaId"
+                  label="media_id">
+          </el-table-column>
+          <el-table-column
+                  prop="name"
+                  label="名称">
+          </el-table-column>
+          <el-table-column
+                  prop="updateTime"
+                  label="更新时间">
+          </el-table-column>
+          <el-table-column
+                  fixed="right"
+                  label="操作">
+            <template slot-scope="scope">
+              <el-button type="text"
+                         icon="el-icon-download"
+                         size="small"
+                         plain
+                         @click="handleDown(scope.row)">下载</el-button>
+              <el-button type="text"
+                         icon="el-icon-delete"
+                         size="small"
+                         plain
+                         @click="handleDelete(scope.row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+        <el-pagination
+                @size-change="sizeChange"
+                @current-change="currentChange"
+                :current-page.sync="queryParams.currentPage"
+                :page-sizes="[10, 20]"
+                :page-size="queryParams.pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="queryParams.total"
+                class="pagination"
+        >
+        </el-pagination>
+      </el-tab-pane>
+      <el-tab-pane name="video">
+        <span slot="label"><i class="el-icon-video-play"></i> 视频</span>
+        <div class="add_but">
+          <el-button size="mini" type="primary" @click="handleAddVideo">新建</el-button>
+        </div>
+        <el-dialog title="新建视频" :visible.sync="dialogVideoVisible" v-loading="addMaterialLoading">
+          <el-upload
+                  ref="uploadVideo"
+                  :action="actionUrl"
+                  :headers="headers"
+                  multiple
+                  :limit="1"
+                  :on-success="handleUploadSuccess"
+                  :file-list="fileList"
+                  :before-upload="beforeVideoUpload"
+                  :auto-upload="false"
+                  :data="uploadData">
+            <el-button slot="trigger" size="mini" type="primary">选择视频</el-button>
+            <div class="el-upload__tip">
+              格式支持MP4,文件大小不超过10MB
+            </div>
+          </el-upload>
+          <el-form :model="uploadData"
+                   :rules="uploadRules"
+                   ref="uploadForm">
+            <el-form-item label="标题" prop="title">
+              <el-input v-model="uploadData.title" placeholder="标题将展示在相关播放页面,建议填写清晰、准确、生动的标题"></el-input>
+            </el-form-item>
+            <el-form-item label="描述" prop="introduction">
+              <el-input :rows="3" type="textarea" v-model="uploadData.introduction" placeholder="介绍语将展示在相关播放页面,建议填写简洁明确、有信息量的内容"></el-input>
+            </el-form-item>
+          </el-form>
+          <div slot="footer" class="dialog-footer">
+            <el-button @click="dialogVideoVisible = false">取 消</el-button>
+            <el-button type="primary" @click="subVideo">提 交</el-button>
+          </div>
+        </el-dialog>
+        <el-table
+                :data="list"
+                stripe
+                border
+                v-loading="loading">
+          <el-table-column
+                  prop="mediaId"
+                  label="media_id">
+          </el-table-column>
+          <el-table-column
+                  prop="name"
+                  label="名称">
+          </el-table-column>
+          <el-table-column
+                  prop="updateTime"
+                  label="更新时间">
+          </el-table-column>
+          <el-table-column
+                  fixed="right"
+                  label="操作">
+            <template slot-scope="scope">
+              <el-button type="text"
+                         icon="el-icon-view"
+                         size="small"
+                         plain
+                         @click="handleInfo(scope.row)">查看</el-button>
+              <el-button type="text"
+                         icon="el-icon-delete"
+                         size="small"
+                         plain
+                         @click="handleDelete(scope.row)">删除</el-button>
+            </template>
+          </el-table-column>
+        </el-table>
+        <el-pagination
+                @size-change="sizeChange"
+                @current-change="currentChange"
+                :current-page.sync="queryParams.currentPage"
+                :page-sizes="[10, 20]"
+                :page-size="queryParams.pageSize"
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="queryParams.total"
+                class="pagination"
+        >
+        </el-pagination>
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import WxVoicePlayer from '@/views/mp/components/wx-voice-play/main.vue';
+import WxVideoPlayer from '@/views/mp/components/wx-video-play/main.vue';
+import { getSimpleAccounts } from "@/api/mp/account";
+import { getMaterialPage, deletePermanentMaterial } from "@/api/mp/material";
+import { getAccessToken } from '@/utils/auth'
+
+export default {
+  name: 'mpMaterial',
+  components: {
+    WxVoicePlayer,
+    WxVideoPlayer
+  },
+  data() {
+    return {
+      type: 'image',
+      // 遮罩层
+      loading: false,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 数据列表
+      list: [],
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+        accountId: undefined,
+      },
+
+      actionUrl: process.env.VUE_APP_BASE_API  + '/admin-api/mp/material/upload-permanent',
+      headers: { Authorization: "Bearer " + getAccessToken() }, // 设置上传的请求头部
+      fileList:[],
+      dialogVideoVisible:false,
+      dialogNewsVisible:false,
+      addMaterialLoading:false,
+      uploadData: {
+        "type": 'image',
+        "title":'',
+        "introduction":''
+      },
+      uploadRules:{
+        title: [
+          { required: true, message: '请输入标题', trigger: 'blur' }
+        ],
+        introduction: [
+          { required: true, message: '请输入描述', trigger: 'blur' }
+        ],
+      },
+
+      // 公众号账号列表
+      accounts: [],
+    }
+  },
+  created() {
+    getSimpleAccounts().then(response => {
+      this.accounts = response.data;
+      // 默认选中第一个
+      if (this.accounts.length > 0) {
+        this.setAccountId(this.accounts[0].id);
+      }
+      // 加载数据
+      this.getList();
+    })
+  },
+  methods: {
+    // ======================== 列表查询 ========================
+    /** 设置账号编号 */
+    setAccountId(accountId) {
+      console.log('奥特曼!')
+      this.queryParams.accountId = accountId;
+      this.uploadData.accountId = accountId;
+    },
+    /** 查询列表 */
+    getList() {
+      // 如果没有选中公众号账号,则进行提示。
+      if (!this.queryParams.accountId) {
+        this.$message.error('未选中公众号,无法查询草稿箱')
+        return false
+      }
+
+      this.loading = true
+      getMaterialPage({
+        ...this.queryParams,
+        type: this.type
+      }).then(response => {
+        this.list = response.data.list
+        this.total = response.data.total
+      }).finally(() => {
+        this.loading = false
+      })
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1
+      // 默认选中第一个
+      if (this.queryParams.accountId) {
+        this.setAccountId(this.queryParams.accountId)
+      }
+      this.getList()
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm('queryForm')
+      // 默认选中第一个
+      if (this.accounts.length > 0) {
+        this.setAccountId(this.accounts[0].id)
+      }
+      this.handleQuery()
+    },
+    handleClick(tab, event) {
+      // 设置 type
+      this.uploadData.type = tab.name
+      // 从第一页开始查询
+      this.handleQuery();
+    },
+
+    // ======================== 文件上传 ========================
+
+    handleInfo(row){
+      this.loading = true
+      getMaterialVideo({
+        mediaId:row.mediaId
+      }).then((response) => {
+        this.loading = false
+        if(response.code == 200){
+          let downUrl = response.data.downUrl
+          window.open(downUrl, '_blank');
+        }else{
+          this.$message.error('获取微信视频素材出错:' + response.data.msg)
+        }
+      }).catch(() => {
+        this.loading = false
+      })
+    },
+    handleDown(row){
+      this.loading = true
+      getMaterialOther({
+        mediaId:row.mediaId,
+        fileName:row.name
+      }).then(response => {
+        this.loading = false
+        let url = window.URL.createObjectURL(new Blob([response.data]))
+        let link = document.createElement('a')
+        link.style.display = 'none'
+        link.href = url
+        link.setAttribute('download', row.name)
+        document.body.appendChild(link)
+        link.click()
+      }).catch(() => {
+        this.loading = false
+      })
+    },
+    subVideo(){
+      this.$refs['uploadForm'].validate((valid) => {
+        if (valid) {
+          this.$refs.uploadVideo.submit()
+        } else {
+          return false
+        }
+      })
+    },
+    handleAddVideo(){
+      this.dialogVideoVisible = true
+    },
+    beforeImageUpload(file) {
+      const isType = file.type === 'image/jpeg'
+          || file.type === 'image/png'
+          || file.type === 'image/gif'
+          || file.type === 'image/bmp'
+          || file.type === 'image/jpg';
+      if (!isType) {
+        this.$message.error('上传图片格式不对!')
+        this.loading = false
+        return false;
+      }
+      const isLt = file.size / 1024 / 1024 < 2
+      if (!isLt) {
+        this.$message.error('上传图片大小不能超过 2M!')
+        this.loading = false
+        return false;
+      }
+      this.loading = true
+      return true;
+    },
+    beforeVoiceUpload(file){
+      const isType = file.type === 'audio/mp3' || file.type === 'audio/wma' || file.type === 'audio/wav' || file.type === 'audio/amr';
+      const isLt = file.size / 1024 / 1024 < 2
+      if (!isType) {
+        this.$message.error('上传语音格式不对!')
+      }
+      if (!isLt) {
+        this.$message.error('上传语音大小不能超过2M!')
+      }
+      this.loading = false
+      return isType && isLt;
+    },
+    beforeVideoUpload(file){
+      this.addMaterialLoading = true
+      const isType = file.type === 'video/mp4'
+      const isLt = file.size / 1024 / 1024 < 10
+      if (!isType) {
+        this.$message.error('上传视频格式不对!')
+      }
+      if (!isLt) {
+        this.$message.error('上传视频大小不能超过10M!')
+      }
+      this.addMaterialLoading = false
+      return isType && isLt;
+    },
+    handleUploadSuccess(response, file, fileList) {
+      this.loading = false
+      this.addMaterialLoading = false
+      if (response.code !== 0) {
+        this.$message.error('上传出错:' + response.msg)
+        return false;
+      }
+
+      // 清空上传时的各种数据
+      this.dialogVideoVisible = false
+      this.fileList = []
+      this.uploadData.title = ''
+      this.uploadData.introduction = ''
+
+      // 加载数据
+      this.getList()
+    },
+
+    // ======================== 其它操作 ========================
+    handleDelete(item) {
+      const id = item.id
+      this.$modal.confirm('此操作将永久删除该文件, 是否继续?').then(function() {
+        return deletePermanentMaterial(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+/*瀑布流样式*/
+.waterfall {
+  width: 100%;
+  column-gap:10px;
+  column-count: 5;
+  margin-top: 10px; /* 芋道源码:增加 10px,避免顶着上面 */
+}
+.waterfall-item {
+  padding: 10px;
+  margin-bottom: 10px;
+  break-inside: avoid;
+  border: 1px solid #eaeaea;
+}
+.material-img {
+  width: 100%;
+}
+p {
+  line-height: 30px;
+}
+@media (min-width: 992px) and (max-width: 1300px) {
+  .waterfall {
+    column-count: 3;
+  }
+  p {
+    color:red;
+  }
+}
+@media (min-width: 768px) and (max-width: 991px) {
+  .waterfall {
+    column-count: 2;
+  }
+  p {
+    color: orange;
+  }
+}
+@media (max-width: 767px) {
+  .waterfall {
+    column-count: 1;
+  }
+}
+/*瀑布流样式*/
+</style>