Explorar o código

Merge branch 'ETA_1.6.9'

hbchen hai 1 ano
pai
achega
f2db0480a3
Modificáronse 34 ficheiros con 3862 adicións e 10 borrados
  1. 7 0
      config/index.js
  2. 239 1
      src/api/modules/semanticsApi.js
  3. BIN=BIN
      src/assets/img/asr.png
  4. BIN=BIN
      src/assets/img/document_m/audio-backward.png
  5. BIN=BIN
      src/assets/img/document_m/audio-forward.png
  6. BIN=BIN
      src/assets/img/document_m/audio-start.png
  7. BIN=BIN
      src/assets/img/document_m/audio-stop.png
  8. BIN=BIN
      src/assets/img/filter.png
  9. BIN=BIN
      src/assets/img/icons/arrow_black_up.png
  10. BIN=BIN
      src/assets/img/icons/arrow_left_black.png
  11. BIN=BIN
      src/assets/img/icons/close.png
  12. BIN=BIN
      src/assets/img/icons/copy-active.png
  13. BIN=BIN
      src/assets/img/icons/copy.png
  14. BIN=BIN
      src/assets/img/icons/eyes-hide-active.png
  15. BIN=BIN
      src/assets/img/icons/eyes-hide.png
  16. BIN=BIN
      src/assets/img/icons/eyes-show-active.png
  17. BIN=BIN
      src/assets/img/icons/eyes-show.png
  18. BIN=BIN
      src/assets/img/more.png
  19. BIN=BIN
      src/assets/img/sand_new/move.png
  20. BIN=BIN
      src/assets/img/tag-gray.png
  21. BIN=BIN
      src/assets/img/tag.png
  22. 28 0
      src/directives/tooltip-hidable.js
  23. 5 0
      src/routes/modules/semanticsRoutes.js
  24. 24 0
      src/utils/buttonConfig.js
  25. 26 9
      src/utils/common.js
  26. 2013 0
      src/views/semantics_manage/asr/ASR.vue
  27. 139 0
      src/views/semantics_manage/asr/components/catalogueDia.vue
  28. 130 0
      src/views/semantics_manage/asr/components/editTag.vue
  29. 117 0
      src/views/semantics_manage/asr/components/exportDia.vue
  30. 91 0
      src/views/semantics_manage/asr/components/fileRename.vue
  31. 249 0
      src/views/semantics_manage/asr/components/makeTags.vue
  32. 186 0
      src/views/semantics_manage/asr/components/mediaCom.vue
  33. 438 0
      src/views/semantics_manage/asr/components/mediaUpload.vue
  34. 170 0
      src/views/semantics_manage/asr/components/transferStatus.vue

+ 7 - 0
config/index.js

@@ -36,6 +36,13 @@ module.exports = {
     assetsSubDirectory: 'static',
     assetsPublicPath: '/',
     proxyTable:{
+    '/voice':{
+      target: "http://localhost:3003",
+      changeOrigin:true, // 如果接口跨域,需要进行这个参数配置
+      pathRewrite:{
+        '^/voice':'/voice'
+      }
+    },
 		'/adminapi': {
       target: "http://8.136.199.33:7778",
       // target: "http://192.168.77.7:8606",

+ 239 - 1
src/api/modules/semanticsApi.js

@@ -277,6 +277,244 @@ const semanticInterface = {
   }
 }
 
+//语音识别
+const asrInterface = {
+  /**
+   * 获取目录的列表
+   * @param {} 
+   * ParentId
+   */
+  getCatalogueList:params=>{
+    return http.get('/speech_recognition/menu/list',params)
+  },
+  /** 
+   * 获取目录的目录树
+   * @param {} 
+   * ParentId
+  */
+  getCatalogueTree:params=>{
+    return http.get('/speech_recognition/menu/tree',params)
+  },
+  /**
+   * 新增目录
+   * @param {} 
+   * ParentId MenuName
+  */ 
+  addCatalogue:params=>{
+    return http.post('/speech_recognition/menu/add',params)
+  }, 
+  /**
+   * 编辑目录
+   * @param {} 
+   * MenuId MenuName
+  */ 
+  editCatalogue:params=>{
+    return http.post('/speech_recognition/menu/edit',params)
+  },
+  /**
+   * 删除目录-检查
+   * @param {} 
+   * MenuId
+  */ 
+  removeCatalogueCheck:params=>{
+    return http.post('/speech_recognition/menu/remove_check',params)
+  },
+  /**
+   * 删除目录
+   * @param {} 
+   * MenuId
+  */ 
+  removeCatalogue:params=>{
+    return http.post('/speech_recognition/menu/remove',params)
+  },
+  /**
+   * 移动目录/文件
+   * @param {} 
+   * MenuId ParentMenuId PrevMenuId NextMenuId SpeechId PrevSpeechId NextSpeechId
+  */ 
+  moveCatalogueEle:params=>{
+    return http.post('/speech_recognition/menu/move',params)
+  },
+  /**
+   * 获取标签的目录列表
+   * @param {} ParentId
+  */
+  getTagCatalogueList:params=>{
+    return http.get('/speech_recognition/tag/menu/list',params)
+  },
+  /**
+   * 新增标签目录
+   * @param {} 
+   * ParentId MenuName
+  */ 
+  addTagCatalogue:params=>{
+    return http.post('/speech_recognition/tag/menu/add',params)
+  }, 
+  /**
+   * 编辑标签目录
+   * @param {} 
+   * MenuId MenuName
+  */ 
+  editTagCatalogue:params=>{
+    return http.post('/speech_recognition/tag/menu/edit',params)
+  },
+  /**
+   * 删除标签目录-检查
+   * @param {} 
+   * MenuId
+  */ 
+  removeTagCatalogueCheck:params=>{
+    return http.post('/speech_recognition/tag/menu/remove_check',params)
+  },
+  /**
+   * 删除标签目录
+   * @param {} 
+   * MenuId
+  */ 
+  removeTagCatalogue:params=>{
+    return http.post('/speech_recognition/tag/menu/remove',params)
+  },
+  /**
+   * 移动标签目录/标签
+   * @param {} 
+   * MenuId ParentMenuId PrevMenuId NextMenuId TagId PrevTagId NextTagId
+  */ 
+  moveTagCatalogueEle:params=>{
+    return http.post('/speech_recognition/tag/menu/move',params)
+  },
+  /**
+   * 获取标签的目录列表
+  */
+  getTagTree:params=>{
+    return http.get('/speech_recognition/tag/menu/tree',params)
+  },
+  /**
+   * 获取标签列表
+   * Keywords
+  */
+  getTagsList:params=>{
+    return http.get('/speech_recognition/tag/list',params)
+  },
+  /**
+   * 新增标签
+   * @param {} 
+   * MenuId TagName
+  */ 
+  addTag:params=>{
+    return http.post('/speech_recognition/tag/add',params)
+  }, 
+  /**
+   * 编辑标签
+   * @param {} 
+   * TagId TagName
+  */ 
+  editTag:params=>{
+    return http.post('/speech_recognition/tag/edit',params)
+  },
+  /**
+   * 删除标签-检查
+   * @param {} 
+   * TagId
+  */ 
+  removeTagCheck:params=>{
+    return http.post('/speech_recognition/tag/remove_check',params)
+  },
+  /**
+   * 删除标签
+   * @param {} 
+   * TagId
+  */ 
+  removeTag:params=>{
+    return http.post('/speech_recognition/tag/remove',params)
+  },
+  /**
+   * 语音识别列表
+   * @param {} 
+   * 
+  */
+  getSpeechList:params=>{
+    return http.get('/speech_recognition/list',params)
+  },
+  /**
+   * 语音识别-待转换列表
+  */
+  getConvertList:params=>{
+    return http.get('/speech_recognition/convert_list',params)
+  },
+  /**
+   * 校验文件是否重命
+   * @param {} 
+   * MenuId Files:[{FileName,ResourceUrl}] 
+  */ 
+  speechFlieRepetitionCheck:params=>{
+    return http.post('/speech_recognition/convert/check_name',params)
+  },
+  /**
+   * 新建 - 批量转写
+   * @param {} 
+   * MenuId Files:[{FileName,ResourceUrl}] 
+  */ 
+  speechTransfer:params=>{
+    return http.post('/speech_recognition/convert',params)
+  },
+  /**
+   * 获取详情
+   * @param {} 
+   * SpeechRecognitionId
+  */ 
+  getSpeechDetail:params=>{
+    return http.get('/speech_recognition/detail',params)
+  },
+  /**
+   * 重命名
+   * @param {} 
+   * SpeechRecognitionId FileName
+  */ 
+  speechRename:params=>{
+    return http.post('/speech_recognition/rename',params)
+  },
+  /**
+   * 导出 
+   * /speech_recognition/export
+   * SpeechRecognitionId
+   * ExportType 导出类型:1-txt;2-doc;3-pdf 
+   * Timestamp:boolean
+  */ 
+  /**
+   * 删除
+   * @param {} 
+   * SpeechRecognitionId
+  */ 
+  speechRemove:params=>{
+    return http.post('/speech_recognition/remove',params)
+  },
+  /**
+   * 保存标签
+   * @param {} 
+   * SpeechRecognitionId
+   * TagIds:Array
+  */ 
+  speechSaveTag:params=>{
+    return http.post('/speech_recognition/save_tag',params)
+  },
+  /**
+   * 删除音视频
+   * @param {} 
+   * SpeechRecognitionId
+  */ 
+  speechMediaRemove:params=>{
+    return http.post('/speech_recognition/remove_file',params)
+  },
+  /**
+   * 保存
+   * @param {} 
+   * SpeechRecognitionId FileName TagIds:[number] Contents:[{SpeechRecognitionContentId,Content}] 
+  */ 
+  speechSave:params=>{
+    return http.post('/speech_recognition/save',params)
+  },
+}
+
 
 
-export {tagInterface,documentInterface,semanticInterface}
+export {tagInterface,documentInterface,semanticInterface,asrInterface}

BIN=BIN
src/assets/img/asr.png


BIN=BIN
src/assets/img/document_m/audio-backward.png


BIN=BIN
src/assets/img/document_m/audio-forward.png


BIN=BIN
src/assets/img/document_m/audio-start.png


BIN=BIN
src/assets/img/document_m/audio-stop.png


BIN=BIN
src/assets/img/filter.png


BIN=BIN
src/assets/img/icons/arrow_black_up.png


BIN=BIN
src/assets/img/icons/arrow_left_black.png


BIN=BIN
src/assets/img/icons/close.png


BIN=BIN
src/assets/img/icons/copy-active.png


BIN=BIN
src/assets/img/icons/copy.png


BIN=BIN
src/assets/img/icons/eyes-hide-active.png


BIN=BIN
src/assets/img/icons/eyes-hide.png


BIN=BIN
src/assets/img/icons/eyes-show-active.png


BIN=BIN
src/assets/img/icons/eyes-show.png


BIN=BIN
src/assets/img/more.png


BIN=BIN
src/assets/img/sand_new/move.png


BIN=BIN
src/assets/img/tag-gray.png


BIN=BIN
src/assets/img/tag.png


+ 28 - 0
src/directives/tooltip-hidable.js

@@ -0,0 +1,28 @@
+// 长度溢出显示ToolTip 请绑定的标签元素包裹住 element-ui的el-tooltip。
+/**
+ * binding.value 为长度溢出的元素的css选择器,没有请设置
+ */
+import { Message } from "element-ui"
+
+export default {
+  bind(el,binding,vnode) {
+    try {
+      // console.log(vnode,'vnode');
+      el.onmouseenter=(e)=>{
+        // console.log(e);
+        if(el.scrollWidth > el.clientWidth){
+          // 超出
+          vnode.componentInstance.disabled=false
+        }else{
+          vnode.componentInstance.disabled=true
+        }
+      }
+    } catch (error) {
+      console.error(error);
+      Message.error(error.message)
+    }
+  },
+  updated(el,binding,vnode) {
+    console.log('updated');
+  },
+}

+ 5 - 0
src/routes/modules/semanticsRoutes.js

@@ -59,6 +59,11 @@ export default [{
             pathName:'文档对比',
             keepAlive: false
         }
+    },
+    {
+      path: "ASR",
+      name: "语音识别",
+      component: () => import('@/views/semantics_manage/asr/ASR.vue')
     }
   ]
 }]

+ 24 - 0
src/utils/buttonConfig.js

@@ -482,6 +482,30 @@ export const semanticPermission = {
     /*-----------标签管理--------- */
     tagPage_del:'tagPage:del',
     tagPage_save:'tagPage:save',//添加编辑标签
+    /*-----------语音识别--------- */
+    ASR_view:'ASR:view',//查看
+    ASR_classify_catalogue_add:'ASR:classify:catalogue:add',//目录树添加
+    ASR_classify_catalogue_edit:'ASR:classify:catalogue:edit',//目录树编辑
+    ASR_classify_catalogue_del:'ASR:classify:catalogue:del',//目录树删除
+    ASR_classify_catalogue_move:'ASR:classify:catalogue:move',//目录树拖动 
+    ASR_classify_tag_add:'ASR:classify:tag:add',//标签目录树添加
+    ASR_classify_tag_edit:'ASR:classify:tag:edit',//标签目录树编辑
+    ASR_classify_tag_del:'ASR:classify:tag:del',//标签目录树删除
+    ASR_classify_tag_move:'ASR:classify:tag:move',//标签目录树拖动 
+    ASR_tagOperation_addTag:'ASR:tagOperation:addTag',//标签添加
+    ASR_tagOperation_editTag:'ASR:tagOperation:editTag',//标签编辑
+    ASR_tagOperation_deleteTag:'ASR:tagOperation:deleteTag',//标签删除
+    ASR_addArticles:'ASR:addArticles',//新建
+    ASR_renameArticles:'ASR:renameArticles',//重命名
+    ASR_exportArticles:'ASR:exportArticles',//导出
+    ASR_deleteArticles:'ASR:deleteArticles',//删除
+    ASR_tagMark_view:'ASR:tagMark:view',//打标签-查看标签
+    ASR_tagMark_add:'ASR:tagMark:add',//打标签-添加标签    
+    ASR_saveArticles:'ASR:saveArticles',//保存
+    ASR_deleteVideo:'ASR:deleteVideo',//删除视频
+    ASR_deleteVoice:'ASR:deleteVoice',//删除音频
+    ASR_copyText:'ASR:copyText',//复制
+    ASR_toggleTimestampShow:'ASR:toggleTimestampShow',//隐藏/显示时间戳
 }
 /*
  * --------------------------------------------------------------------------统计分析------------------------------------------------

+ 26 - 9
src/utils/common.js

@@ -179,15 +179,20 @@ const handleUploadToMinIO=(file,fileName,options={})=>{
     
       minioClient.bucketExists(res.Data.Bucketname, function (err, exists) {
           if (err) {
-              throw "minio 查看桶是否存在失败"+err
-              // return console.log(err);
+              Message.warning("上传失败,请刷新重试");
+              reject('minio 查看桶是否存在失败')
+              // throw "minio 查看桶是否存在失败"+err
+              return console.error("minio 查看桶是否存在失败"+err);
           }
           if (!exists) {
             // 不存在桶,创建桶
             console.log("桶不存在,先创建桶",res.Data.Bucketname);
             minioClient.makeBucket(res.Data.Bucketname, res.Data.RegionId,function (err) {
               if (err) {
-                throw "minio 创建桶失败"+err
+                Message.warning("上传失败,请刷新重试");
+                reject('minio 创建桶失败')
+                return console.error("minio 创建桶失败"+err);
+                // throw "minio 创建桶失败"+err
               }
             
               let reader = new FileReader();
@@ -201,7 +206,10 @@ const handleUploadToMinIO=(file,fileName,options={})=>{
                 // console.log(bufferStream);
                 minioClient.putObject(res.Data.Bucketname, fileName, bufferStream,file.size, metaData, function (err, etag) {
                   if (err){
-                    throw "上传到minio失败:"+err
+                    Message.warning("上传失败,请刷新重试");
+                    reject('上传到minio失败')
+                    return console.error("上传到minio失败"+err);
+                    // throw "上传到minio失败:"+err
                   }
             
                 })
@@ -220,7 +228,10 @@ const handleUploadToMinIO=(file,fileName,options={})=>{
               bufferStream.end(Buffer.from(dataurl))
               minioClient.putObject(res.Data.Bucketname, fileName, bufferStream, metaData, function (err, etag) {
                 if (err){
-                  throw "上传到minio失败:"+err
+                  Message.warning("上传失败,请刷新重试");
+                  reject('上传到minio失败')
+                  return console.error("上传到minio失败"+err);
+                  // throw "上传到minio失败:"+err
                 }
                 let fileUrl = fileName.startsWith('/')?res.Data.ImgHost+fileName:res.Data.ImgHost+"/"+fileName
                 resolve(fileUrl)
@@ -232,7 +243,7 @@ const handleUploadToMinIO=(file,fileName,options={})=>{
       console.error(error);
       if (error.name !== "cancel") {
         //不是取消上传的则给错误提示
-        this.$message.warning("上传失败,请刷新重试");
+        Message.warning("上传失败,请刷新重试");
       }
       reject(error)
     }
@@ -240,6 +251,10 @@ const handleUploadToMinIO=(file,fileName,options={})=>{
 }
 //https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
 const handleUploadToS3=(file,fileName,options={})=>{
+  /**
+   * options 里面包含了一些配置,包括分片上传等…… 测试服目前还没有AWS3 的对象存储器,无法测试,暂不进行配置
+   * s3Client.upload(params={},[options={}],[callback])
+   */
     return new Promise(async(resolve,reject)=>{
         const res=await getOSSSign({StorageSource:3})
         if(res.Ret!==200) reject("获取s3签名错误,res.Ret="+res.Ret)
@@ -296,8 +311,10 @@ const handleUploadToS3=(file,fileName,options={})=>{
                     Key: fileName
                 }, function (err, data) {
                     if (err) {
-                        console.log("Error! err =====> ", err);
-                        throw new Error("上传到s3失败!")
+                        Message.warning("上传失败,请刷新重试");
+                        reject('上传到s3失败')
+                        return console.log("上传到s3失败! Error! err =====> ", err);
+                        // throw new Error("上传到s3失败!")
                     } else {
                         let url = data['Location'];
                         console.log("Successfully uploaded! URL =====> ", url);
@@ -309,7 +326,7 @@ const handleUploadToS3=(file,fileName,options={})=>{
             console.error(error);
             if (error.name !== "cancel") {
               //不是取消上传的则给错误提示
-              this.$message.warning("上传失败,请刷新重试");
+              Message.warning("上传失败,请刷新重试");
             }
             reject(error)
           }

+ 2013 - 0
src/views/semantics_manage/asr/ASR.vue

@@ -0,0 +1,2013 @@
+<template>
+  <div id="asr-container">
+    <div class="asr-main">
+      <div class="asr-main-left">
+        <div class="add-zone" >
+          <el-button @click="addAsr" type="primary" style="min-width: 100px;"
+          v-permission="permissionBtn.semanticPermission.ASR_addArticles">新建</el-button>
+        </div>
+        <div class="catalogue-zone" v-loading="detailLoadig" element-loading-text="加载中······">
+          <div class="search-box">
+            <el-select v-model="search_txt" v-loadMore="searchLoad" :filterable="!search_txt" remote clearable
+              placeholder="请输入文件名称" class="select-com" :remote-method="searchHandle" @click.native="inputFocusHandle">
+              <i slot="prefix" class="el-input__icon el-icon-search"></i>
+              <el-option v-for="item in searchOptions" :key="item.SpeechRecognitionId" :label="item.FileName"
+                :value="item.SpeechRecognitionId">
+              </el-option>
+            </el-select>
+            <el-popover placement="bottom-end" :offset="10" width="334" v-model="showFilterPopopver"
+              trigger="click" @hide="resetFilterOption" @show="getTagsList">
+                <div class="filter-poopver">
+                  <div class="filter-poopver-header">
+                    <span>筛选项</span>
+                    <span style="cursor: pointer;color: #0052D9;" @click="resetFilterOption('clear')">清空</span>
+                  </div>
+                  <el-form :model="filterForm" ref="filterForm" class="filter-poopver-form">
+                    <el-form-item prop="createtimeRange">
+                      <el-date-picker v-model="filterForm.createtimeRange" start-placeholder="开始" end-placeholder="结束"
+                      type="daterange" style="width: 100%;" value-format="yyyy-MM-dd"></el-date-picker>
+                    </el-form-item>
+                    <el-form-item prop="author" >
+                      <el-cascader v-model="filterForm.author" placeholder="创建人" ref="authorsCascader" clearable 
+                      id="authors-cascader" style="width: 100%" :options="authorsList" collapse-tags :show-all-levels="false"
+                      :props="{value:'ItemId',label:'ItemName',children:'Children',multiple:true,emitPath:false}"></el-cascader>
+                      <!-- <el-select v-model="filterForm.author" placeholder="创建人" style="width: 100%;" multiple
+                      collapse-tags filterable>
+                        <el-option :label="item.authorName" :value="item.authorId"
+                        v-for="item in authorsList" :key="item.authorId" ></el-option>
+                      </el-select> -->
+                    </el-form-item>
+                    <el-form-item prop="tags" v-if="leftShowLabel !== '标签'">
+                      <el-select v-model="filterForm.tags" placeholder="标签" style="width: 100%;" multiple
+                      collapse-tags filterable>
+                        <el-option :label="item.TagName" :value="item.TagId" 
+                        v-for="item in tagsList" :key="item.TagId"></el-option>
+                      </el-select>
+                    </el-form-item>
+                  </el-form>
+                  <div class="filter-poopver-footer">
+                    <el-button type="primary" plain style="width: 140px;"  @click="showFilterPopopver=false">取消</el-button>
+                    <el-button type="primary" style="width: 140px;" @click="filterArticle">确认</el-button>
+                  </div>
+                </div>
+              <div class="filter-box" slot="reference">
+                <img src="~@/assets/img/filter.png" />
+                <span>筛选</span>
+              </div>
+            </el-popover>
+          </div>
+          <div class="catalogue-head">
+            <ul class="left-tab">
+              <li :class="['tab', { 'act': leftShowLabel === tab }]" v-for="tab in ['目录', '标签']" :key="tab"
+                @click="tabChange(tab)">{{ tab }}</li>
+            </ul>
+          </div>
+          <div v-show="leftShowLabel=='目录'" class="tree-container">
+            <div class="tree-cont">
+              <el-tree ref="treeRef" class="target_tree" :data="treeData" node-key="UniqueCode" :props="defaultProp"
+                :allow-drag="canDragHandle" :allow-drop="canDropHandle" :current-node-key="select_node_key"
+                :default-expanded-keys="defaultShowNodes" draggable :expand-on-click-node="false" check-strictly
+                empty-text="暂无分类" lazy :load="getLazyTreeData" @node-expand="handleNodeExpand"
+                @node-collapse="handleNodeCollapse" @current-change="nodeChange" @node-drop="dropOverHandle"
+                @node-drag-end="dropMouseLeave" @node-drag-leave="dropMouseLeave" @node-drag-enter="dropMouseOver">
+                <span class="custom-tree-node" slot-scope="{ node, data }">
+                  <span class="text-span">{{ data.NodeType==0?data.MenuName:data.SpeechRecognitionName }}</span>
+                  <span v-if="select_node_key === data.UniqueCode" class="operation-span">
+                    <img src="~@/assets/img/sand_new/move.png"/>
+                    <!-- 添加子项 -->
+                    <img src="~@/assets/img/sand_new/add_outline.png" @click.stop="addNode(node, data)" 
+                    v-if="data.Level<3 && data.NodeType==0 && permissionBtn.isShowBtn('semanticPermission', 'ASR_classify_catalogue_add')"/>
+                    <!-- 编辑子项 -->
+                    <img src="~@/assets/img/sand_new/edit_outline.png" @click.stop="editNode(node, data)"
+                    v-if="data.NodeType==0 && permissionBtn.isShowBtn('semanticPermission', 'ASR_classify_catalogue_edit')"/>
+                    <!-- 删除子项 -->
+                    <img slot="reference" src="~@/assets/img/sand_new/delete_outline.png" @click.stop="removeNode(node, data)" 
+                    v-if="data.NodeType==0 && permissionBtn.isShowBtn('semanticPermission', 'ASR_classify_catalogue_del')" style="margin-right: 0;"/>
+                  </span>
+                </span>
+              </el-tree>
+            </div>
+            <div class="bottom-operation" v-permission="permissionBtn.semanticPermission.ASR_classify_catalogue_add">
+              <div class="bottom-add" @click="editCatalogue(1)">
+                <img src="~@/assets/img/add-quadrate-blue.png" />
+                <span>添加目录分类</span>
+              </div>
+            </div>
+          </div>
+          <div v-show="leftShowLabel=='标签'" class="tree-container">
+            <div class="tree-cont">
+              <el-tree ref="tagTreeRef" class="target_tree" :data="tagTreeData" node-key="UniqueCode" :props="defaultProp"
+                :allow-drag="canDragHandle" :allow-drop="canDropHandle" :current-node-key="tag_select_node_key"
+                :default-expanded-keys="tagDefaultShowNodes" draggable :expand-on-click-node="false" check-strictly
+                empty-text="暂无分类" lazy :load="getLazyTreeTagData" @node-expand="handleNodeExpand"
+                @node-collapse="handleNodeCollapse" @current-change="nodeChange" @node-drop="dropOverHandle"
+                @node-drag-end="dropMouseLeave" @node-drag-leave="dropMouseLeave" @node-drag-enter="dropMouseOver">
+                <span class="custom-tree-node" slot-scope="{ node, data }">
+                  <span class="text-span">{{ data.NodeType==0?data.MenuName:data.TagName }}</span>
+                  <span v-if="tag_select_node_key === data.UniqueCode" class="operation-span">
+                    <img src="~@/assets/img/sand_new/move.png"/>
+                    <!-- 添加子项 -->
+                    <img src="~@/assets/img/sand_new/add_outline.png" @click.stop="addNode(node, data)"
+                    v-if="data.Level<3 && data.NodeType==0 && permissionBtn.isShowBtn('semanticPermission', 'ASR_classify_tag_add')"/>
+                    <!-- 编辑子项 -->
+                    <img src="~@/assets/img/sand_new/edit_outline.png" @click.stop="editNode(node, data)"
+                    v-if="data.TagId ? 
+                          permissionBtn.isShowBtn('semanticPermission', 'ASR_tagOperation_editTag'):
+                          permissionBtn.isShowBtn('semanticPermission', 'ASR_classify_tag_edit')"/>
+                    <!-- 删除子项 -->
+                    <img slot="reference" src="~@/assets/img/sand_new/delete_outline.png" @click.stop="removeNode(node, data)" style="margin-right: 0;"
+                    v-if="data.TagId ? 
+                    permissionBtn.isShowBtn('semanticPermission', 'ASR_tagOperation_deleteTag'):
+                    permissionBtn.isShowBtn('semanticPermission', 'ASR_classify_tag_del')"/>
+                  </span>
+                </span>
+              </el-tree>
+            </div>
+            <div class="bottom-operation">
+              <div class="bottom-add" @click="editCatalogue(1)" style="margin-right: 20px;" 
+              v-permission="permissionBtn.semanticPermission.ASR_classify_tag_add">
+                <img src="~@/assets/img/add-quadrate-blue.png" />
+                <span>添加标签分类</span>
+              </div>
+              <div class="bottom-add" @click="editTag" v-permission="permissionBtn.semanticPermission.ASR_tagOperation_addTag">
+                <img src="~@/assets/img/add-quadrate-blue.png" />
+                <span>添加标签</span>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <!-- 详情 -->
+      <div class="asr-main-right detail" v-loading="detailLoadig" v-permission="permissionBtn.semanticPermission.ASR_view"
+      v-if="articleDetail && articleDetail.SpeechRecognitionId">
+        <div class="detail-top">
+          <div class="detail-top-search">
+            <el-input v-model="articleDetail.FileName" placeholder="请输入文件名称" style="width: 240px;margin-right: 30px;"
+            :disabled="!permissionBtn.isShowBtn('semanticPermission', 'ASR_renameArticles')"></el-input>
+            <div class="tag-button" @click="makeTagsDiaOpen(null)" style="margin-right: 30px;cursor: pointer;"
+            v-permission="permissionBtn.semanticPermission.ASR_tagMark_view">
+              <img src="~@/assets/img/tag.png" />
+              <span style="color: #0052D9;">打标签</span>
+            </div>
+            <el-input v-model="keyWord" placeholder="请输入关键词" prefix-icon="el-icon-search" clearable
+            style="width: 360px;margin-right: 30px;" @input="searchKeyword">
+              <template slot="append">
+                <div class="toggle-keywords-box" v-if="keyWord">
+                  <img src="~@/assets/img/icons/arrow_left_black.png" @click="last"/>
+                  <span>{{ num }}/{{ wordList?wordList.length:0 }}</span>
+                  <img src="~@/assets/img/icons/arrow_left_black.png" style="transform: rotate(180deg);" 
+                  @click="next"/>
+                </div>
+              </template>
+            </el-input>
+          </div>
+          <el-button type="primary" style="width: 120px;" @click="save" :loading="saveLoading" 
+          v-permission="permissionBtn.semanticPermission.ASR_saveArticles">保存</el-button>
+        </div>
+        <div class="detail-tag-area">
+          <div class="tag-button" v-for="(item,index) in articleDetail.Tags" :key="item.TagId" >
+            <img src="~@/assets/img/tag-gray.png" />
+            <span>{{ item.TagName }}</span>
+            <img src="~@/assets/img/icons/close.png" class="tag-close-icon" @click="deleleTag(item,index)"/>
+          </div>
+        </div>
+        <!-- 视频 -->
+        <div class="detail-video-area" v-if="articleDetail && articleDetail.type==2 && articleDetail.ResourceUrl"
+        v-show="!videoHidden">
+          <media-com :ResourceUrl="articleDetail.ResourceUrl" :type="articleDetail.type" ref="mediaRef" />
+        </div>
+        <div class="detail-content">
+          <div v-if="articleDetail && articleDetail.type==2" class="deail-top-operation">
+            <div class="content-row-operation active" @click="videoHidden=!videoHidden" style="margin-right: 0;" v-if="articleDetail.ResourceUrl">
+              <img :src="videoHidden?require('@/assets/img/icons/eyes-show-active.png'):require('@/assets/img/icons/eyes-hide-active.png')" />
+              <span>{{ videoHidden?"显示":"隐藏" }}</span>
+            </div>
+            <div class="content-row-operation drange" @click="deleteMedia" style="margin-right: 0;" v-if="articleDetail.ResourceUrl"
+            v-permission="permissionBtn.semanticPermission.ASR_deleteVideo">
+              <img src="~@/assets/img/icons/delete-red.png" />
+              <span>删除</span>
+            </div>
+          </div>
+          <div class="content-mutil-operation">
+            <el-checkbox label="全选" style="margin-right: 30px;" @change="checkAll"
+            v-model="isCheckAll" :indeterminate="isIndeterminate"></el-checkbox>
+            <div class="content-row-operation active" @click="copyText(null)" v-show="checkList && checkList.length>0"
+              v-permission="permissionBtn.semanticPermission.ASR_copyText">
+              <img src="~@/assets/img/icons/copy-active.png" />
+              <span>复制</span>
+            </div>
+            <div class="content-row-operation active" v-show="checkList && checkList.length>0"
+              v-permission="permissionBtn.semanticPermission.ASR_toggleTimestampShow" 
+              @click="toggleTimestampShow(null)">
+              <img :src="hideTimestampAll?require('@/assets/img/icons/eyes-show-active.png'):require('@/assets/img/icons/eyes-hide-active.png')" />
+              <span>{{ hideTimestampAll?"展示时间戳":"隐藏时间戳" }}</span>
+            </div>
+          </div>
+          <div class="content-title">原文</div>
+          <div class="content-main" :style="{minHeight:articleDetail.ResourceUrl?'490px':'560px'}" ref="contentMain">
+            <div class="content-row" v-for="(item,index) in articleDetail.Contents" :key="item.SpeechRecognitionContentId"
+            @mouseover="item.IsHover=true" @mouseout="item.IsHover=false" :id="'paragraph_'+(index+1)">
+              <div class="content-row-info">
+                <div class="content-row-time" :style="{visibility:item.HideTimestamp?'hidden':'visible'}">
+                  <img src="~@/assets/img/asr.png" class="content-info-speaker"/>
+                  <div class="content-info-start">{{ item.StartTime }}</div>
+                </div>
+                <div class="content-row-operation" :class="{'active':item.IsHover}" @click="copyText(item)"
+                v-permission="permissionBtn.semanticPermission.ASR_copyText">
+                  <img :src="item.IsHover?require('@/assets/img/icons/copy-active.png'):require('@/assets/img/icons/copy.png')" />
+                  <span>复制</span>
+                </div>
+                <div class="content-row-operation" :class="{'active':item.IsHover}" @click="toggleTimestampShow(item)"
+                v-permission="permissionBtn.semanticPermission.ASR_toggleTimestampShow" >
+                  <img :src="item.HideTimestamp?require('@/assets/img/icons/eyes-show.png'):require('@/assets/img/icons/eyes-hide.png')" 
+                  v-show="!item.IsHover"/>
+                  <img :src="item.HideTimestamp?require('@/assets/img/icons/eyes-show-active.png'):require('@/assets/img/icons/eyes-hide-active.png')" 
+                  v-show="item.IsHover"/>
+                  <span>{{ item.HideTimestamp?"展示时间戳":"隐藏时间戳" }}</span>
+                </div>
+              </div>
+              <div class="content-row-content" >
+                <el-checkbox :label="item.SpeechRecognitionContentId" v-model="checkList" @change="checkSingle">{{ '' }}</el-checkbox>
+                <div class="content-row-text"  contenteditable @blur="(e)=> updateData(e,item,index)"
+                :style="{backgroundColor:paragraphIndex==(index+1)?'#ECF2FE':'unset'}"
+                >{{ item.Content }}</div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <!-- 音频 -->
+        <div class="detail-audio-area" v-if="articleDetail && articleDetail.type==1 && articleDetail.ResourceUrl">
+          <media-com :ResourceUrl="articleDetail.ResourceUrl" :type="articleDetail.type" ref="mediaRef" />
+          <img src="~@/assets/img/icons/delete-red.png" @click="deleteMedia" v-permission="permissionBtn.semanticPermission.ASR_deleteVoice" />
+        </div>
+      </div>
+      <!-- 列表 -->
+      <div class="asr-main-right" v-else v-permission="permissionBtn.semanticPermission.ASR_view" 
+      v-infinite-scroll="handleLoadMore" :infinite-scroll-immediate="true" :infinite-scroll-disabled="articleListRequest.finished">
+        <template v-if="articleList && articleList.length>0">
+          <div class="file-count">共{{ total }}个文件</div>
+          <div class="file-container">
+            <div class="file-item" v-for="item in articleList" :key="item.SpeechRecognitionId" @click="switchDetail(item)">
+              <div class="file-main">
+                <el-tooltip :content="item.FileName" placement="top" v-tooltipHidable>
+                  <div class="file-name">
+                    {{ item.FileName }}
+                  </div>
+                </el-tooltip>
+                <div class="file-tag-list">
+                  <div class="file-tag" v-for="tag in item.Tags" 
+                  :key="tag.TagId">{{ tag.TagName }}</div>
+                </div>
+                <div class="file-content">
+                  {{ item.Abstract }}
+                </div>
+              </div>
+              <div class="file-footer">
+                <div class="file-time">
+                  <span >{{ item.CreateTime }}</span>
+                  <span class="split-element">{{ item.FileSecond }}</span>
+                  <span class="split-element">{{ item.FileSize }}</span>
+                </div>
+                <div class="file-author">{{ item.SysUserName }}</div>
+                <div class="file-operation">
+                  <el-dropdown @command="(e)=>handleCommandMore(e,item)">
+                    <img src="~@/assets/img/more.png" />
+                    <el-dropdown-menu slot="dropdown">
+                      <el-dropdown-item command="rename" v-permission="permissionBtn.semanticPermission.ASR_renameArticles">重命名</el-dropdown-item>
+                      <el-dropdown-item command="tag" v-permission="permissionBtn.semanticPermission.ASR_tagMark_view">打标签</el-dropdown-item>
+                      <el-dropdown-item command="export" v-permission="permissionBtn.semanticPermission.ASR_exportArticles">导出</el-dropdown-item>
+                      <el-dropdown-item command="del" v-permission="permissionBtn.semanticPermission.ASR_deleteArticles">删除</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </el-dropdown>
+                </div>
+              </div>
+            </div>
+          </div>
+        </template>
+        <div v-else class="nodata">
+          <tableNoData text="暂无记录"/>
+        </div>
+      </div>
+    </div>
+
+    <!-- 上传音视频弹窗 -->
+    <media-upload :dia-show.sync="mediaUploadDiaShow"  @uploadSuccess="uploadSuccess" :articleCatalogue="articleCatalogue"/>
+    <!-- 目录弹窗 -->
+    <catalogue-dia :dia-show.sync="catalogueDiaShow" :form-data="catalogueData" @catalogueSuccess="catalogueSuccess"/>
+    <!-- 编辑标签 -->
+    <edit-tag :dia-show.sync="tagDiaShow" :form-data="tagData" @tagSuccess="tagSuccess" :tagCatalogue="tagCatalogue"/>
+    <!-- 重命名 -->
+    <file-rename :dia-show.sync="renameDiaShow" :form-data="renameData" @renameSuccess="rename"/>
+    <!-- 导出 -->
+    <export-dia :dia-show.sync="exportDiaShow" :form-data="exportData"/>
+    <!-- 打标签 -->
+    <make-tags :dia-show.sync="makeTagsDiaShow" :form-data="makeTagsData" @makeTagsSuccess="makeTagsSuccess"
+      :tagsList="tagsList" :tagCatalogueList="tagCatalogue" ref="makeTagsRef"
+      @addTagSuccess="addTagSuccess" @updateTagsTree="updateTagsTree" />
+    <!-- 转写状态浮窗 -->
+    <transfer-status :windowShow.sync="transferStatusShow" :transferStatus="transferStatus"
+     class="status-window" @deleteRow="deleteRow"/>
+  </div>
+</template>
+
+<script>
+import mediaCom from "./components/mediaCom.vue"
+import catalogueDia from './components/catalogueDia.vue'
+import editTag from './components/editTag.vue'
+import tooltipHidable from "@/directives/tooltip-hidable.js"
+import fileRename from "./components/fileRename.vue"
+import exportDia from "./components/exportDia.vue"
+import makeTags from "./components/makeTags.vue"
+import mediaUpload from './components/mediaUpload.vue';
+import transferStatus from './components/transferStatus.vue';
+
+import {asrInterface} from '@/api/modules/semanticsApi.js';
+import { dataAuthInterface } from "@/api/api.js";
+
+export default {
+  name: "ASR",
+  components:{mediaCom,catalogueDia,editTag,fileRename,exportDia,makeTags,mediaUpload,transferStatus},
+  directives:{
+    tooltipHidable
+  },
+  data() {
+    return {
+      search_txt: '',
+      current_search:'',
+      searchOptions: [],
+      searchHaveMore:false,
+      leftShowLabel: "目录",
+      onlyMe: false,
+      treeData: [],
+      articleCatalogue:[],
+      tagTreeData:[],
+      tagCatalogue:[],
+      tagOnlyMe: false,
+      defaultShowNodes: [], //展开节点
+      tagDefaultShowNodes: [], //标签树展开节点
+      defaultProp: {
+        // label: 'MenuName',
+        children: 'Children',
+        isLeaf:'isLeaf'
+      }, //树结构配置项
+      select_node_key: '', //当前选中的节点
+      tag_select_node_key:'',
+      select_node_data: '', //当前选中的节点数据
+      articleDetail:{},
+      detailLoadig:false,
+      saveLoading:false,
+      // 段落文字内容备份
+      paragraphs:[],
+      articleList:[],
+      page:1,
+      pageSize:20,
+      total:0,
+      searchPage:1,
+      searchPageSize:20,
+      articleParams:{
+        createtimeRange:[],
+        author:[],
+        tags:[]
+      },
+      showFilterPopopver:false,
+      filterForm:{
+        createtimeRange:[],
+        author:[],
+        tags:[]
+      },
+      // 搜索高亮
+      keyWord:"",
+      num:0,
+      wordList:[],
+      authorsList:[],
+      tagsList:[],
+      articleListRequest:{
+        finished:true,
+        loading:false
+      },
+      result:[],
+      currentRow:{},
+      mediaUploadDiaShow:false,
+      // 目录
+      catalogueDiaShow:false,
+      catalogueData:{},
+      // 标签弹窗
+      tagDiaShow:false,
+      tagData:{},
+      // 重命名弹窗
+      renameDiaShow:false,
+      renameData:{},
+      // 导出弹窗
+      exportDiaShow:false,
+      exportData:{},
+      // 打标签弹窗
+      makeTagsDiaShow:false,
+      makeTagsData:{},
+      transferStatus:{},
+      transferStatusShow:false,
+      // 复选操作
+      hideTimestampAll:false,
+      isCheckAll:false,
+      isIndeterminate:false,
+      checkList:[],
+      videoHidden:false,
+      mediaTimer:null,
+      paragraphIndex:0,
+      highlightDom:null
+    }
+  },
+  watch:{
+    paragraphIndex(value){
+      if(value){
+        let showDom=document.getElementById(`paragraph_${value}`)
+        if(showDom){
+          // showDom.scrollIntoView({block:'center',behavior:'smooth'})
+          $(showDom.parentElement).animate({
+            scrollTop:showDom.offsetTop-showDom.parentElement.clientHeight/2+showDom.clientHeight
+          },500)
+        }
+      }
+    },
+    num(value){
+      if(value){
+        this.paragraphIndex=this.wordList[value-1].index+1
+        this.$refs.mediaRef && this.$refs.mediaRef.updateCurrentTime(this.wordList[value-1].startMs/1000)
+      }
+    },
+    select_node_data(value){
+      if((!value) || value.TagId || value.MenuId){ 
+        // 列表
+        this.page=1
+        this.getArticleList()
+      }
+    },
+    /* 选中搜索指标 展开目录 选中指标 展示数据 */
+		search_txt(newval) {
+			if (newval) {
+				let [search_obj] = this.searchOptions.filter(
+					(item) => item.SpeechRecognitionId === newval
+				);
+        this.switchDetail(search_obj)
+			}
+		},
+  },
+  created() {
+    this.getTreeData()
+    this.getAdminList()
+    this.getTransferStatus()
+    this.getArticleList()
+  },
+  methods: {
+    tabChange(tab){
+      this.leftShowLabel = tab
+      this.clearFilterOption()
+      this.search_txt=""
+      if(this.leftShowLabel=="目录"){
+        this.treeData=[]
+        this.getTreeData(true)
+      }else{
+        this.tagTreeData=[]
+        this.getTagTreeData()
+      }
+    },
+    // 获取目录-目录列表
+    getTreeData(getDetail) {
+      asrInterface.getCatalogueList({ParentId:0}).then(res=>{
+        if(res.Ret == 200){
+          this.treeData=res.Data || []
+          this.$nextTick(()=>{
+            this.$refs.treeRef.setCurrentKey(this.select_node_key);
+            this.select_node_data = this.$refs.treeRef.getCurrentNode()
+            // 详情没有通过监听 select_node_data 发请求
+            if(getDetail && this.select_node_data && this.select_node_data.SpeechRecognitionId){
+              this.switchDetail(this.select_node_data)
+            }
+          })
+        }
+      })
+    },
+    // 获取标签-目录列表
+    getTagTreeData(){
+      asrInterface.getTagCatalogueList({ParentId:0}).then(res=>{
+        if(res.Ret == 200){
+          this.tagTreeData=res.Data || []
+          this.$nextTick(()=>{
+            this.$refs.tagTreeRef.setCurrentKey(this.tag_select_node_key);
+            this.select_node_data = this.$refs.tagTreeRef.getCurrentNode()
+          })
+        }
+      })
+    },
+    // 获取目录-目录树
+    getArticleCatalogue(){
+      asrInterface.getCatalogueTree().then(res=>{
+        if(res.Ret == 200){
+          this.articleCatalogue=res.Data || []
+        }
+      })
+    },
+    // 获取标签-目录树
+    getTagCatalogue(){
+      asrInterface.getTagTree().then(res=>{
+        if(res.Ret == 200){
+          this.tagCatalogue=res.Data || []
+        }
+      })
+    },
+    getTagsList(){
+      if(!this.permissionBtn.isShowBtn('semanticPermission','ASR_tagMark_view')) return 
+      asrInterface.getTagsList().then(res=>{
+        if(res.Ret == 200){
+          this.tagsList=res.Data || []
+          if(this.makeTagsDiaShow){
+            setTimeout(()=>{
+              this.$refs.makeTagsRef.updateTagsList()
+              this.$refs.makeTagsRef.tagsCheckedChange()
+            },0)
+          }
+        }
+      })
+    },
+    // 递归节点
+		getNodeParentData(data,arr){
+			if(data.level===0) return
+			arr.unshift(data.data.MenuName)
+			this.getNodeParentData(data.parent,arr)
+		},
+    getAdminList(){
+      dataAuthInterface.userSearch().then(res=>{
+        if(res.Ret == 200){
+          this.authorsList = res.Data||[];
+        }
+      })
+
+    },
+    /**
+     * 添加节点
+     * @param {*} node 
+     * @param {*} data 
+     */
+    addNode(node, data){
+      let arr=[]
+      this.getNodeParentData(node,arr)
+      this.editCatalogue(data.Level+1,{
+        pId:data.MenuId,
+        mId:0,
+        code:data.UniqueCode,
+        first:arr[0]||'',
+        second:arr[1]||'',
+        third:arr[2]||'',
+      })
+    },
+    /**
+     * 编辑节点
+     * @param {*} node 
+     * @param {*} data 
+     */
+    editNode(node, data){
+      if(data.NodeType==1 && this.leftShowLabel=="标签"){
+        // 编辑标签
+        this.editTag({
+          tId:data.TagId,
+          mId:data.ParentId,
+          tName:data.TagName
+        })
+      }else{
+        // 添加/编辑目录
+        let arr=[]
+        this.getNodeParentData(node,arr)
+        this.editCatalogue(data.Level,{
+          pId:data.ParentId,
+          mId:data.MenuId,
+          code:data.UniqueCode,
+          first:arr[0]||'',
+          second:arr[1]||'',
+          third:arr[2]||'',
+        })
+      }
+    },
+    /**
+     * 添加/编辑目录
+     * @param {*} level 第几级目录
+     * @param {*} row 
+     */
+     editCatalogue(level,row){
+      this.catalogueData={
+        level,
+        type:this.leftShowLabel,
+        pId:row?row.pId:0,
+        mId:row?row.mId:0,
+        code:row?row.code:0,
+        first:row?row.first:'',
+        second:row?row.second:'',
+        third:row?row.third:'',
+      }
+      this.catalogueDiaShow=true
+    },
+    catalogueSuccess({type,code}){
+      if(this.leftShowLabel=="目录"){
+        this.getTreeData();
+        if(type == 'add'){
+          if(code && (!this.defaultShowNodes.some(item => item === code))){
+            this.defaultShowNodes.push(code)
+          }
+        }
+      }else{
+        this.getTagTreeData();
+        if(type == 'add'){
+          if(code && (!this.tagDefaultShowNodes.some(item => item === code))){
+            this.tagDefaultShowNodes.push(code)
+          }
+        }
+      }
+
+    },
+    // 删除节点
+    removeNode(node, data){
+      let params={}
+      let apiName=''
+
+      /**
+       * 0 可删除
+       * 1 关联指标
+       * 2 有子目录无指标
+      */
+      let deleteLabelMap={}
+      if(data.TagId){
+        // 标签
+        params={TagId:data.TagId}
+        apiName='removeTagCheck'
+        deleteLabelMap = {
+          0: '确定删除该标签吗?',
+          1: '标签关联转写文件,删除失败!'
+        }
+      }else{
+        // 目录
+        params={MenuId:data.MenuId}
+        apiName=this.leftShowLabel=="目录"?'removeCatalogueCheck':'removeTagCatalogueCheck'
+        deleteLabelMap = {
+						0: '确定删除当前分类吗?',
+						1: `该分类关联${this.leftShowLabel=='目录'?'转写文件':'标签'},删除失败!`,
+						2: '确定删除当前分类及包含的子分类吗?'
+					}
+      }
+      asrInterface[apiName](params).then(res => {
+				if(res.Ret === 200) {
+					if([1].includes(res.Data.CheckResult)) this.$confirm(
+            deleteLabelMap[res.Data.CheckResult],
+            '删除失败',
+            {
+            confirmButtonText: '知道了',
+            showCancelButton:false,
+            type: 'error'
+          })
+					else if([0,2].includes(res.Data.CheckResult)) this.$confirm(
+              deleteLabelMap[res.Data.CheckResult]
+							|| '确定删除吗?', 
+							'提示',
+							{
+							confirmButtonText: '确定',
+							cancelButtonText: '取消',
+							type: 'warning'
+						}).then(() => {
+							this.delHandle(data.MenuId,data.TagId);
+						}).catch(() => {})
+				}
+			})
+    },
+    delHandle(mId,tid){
+      let params={}
+      let apiName=''
+      if(tid){
+        //标签
+        params={TagId:tid}
+        apiName='removeTag'
+      }else if(mId){
+        // 目录
+        params={MenuId:mId}
+        apiName=this.leftShowLabel=='目录'?'removeCatalogue':'removeTagCatalogue'
+      }
+      asrInterface[apiName](params).then(res=>{
+        if(res.Ret == 200){
+          this.leftShowLabel=='目录'?this.getTreeData():this.getTagTreeData()
+          this.$message.success("删除成功")
+        }
+      })
+    },
+    editTag(row){
+      this.getTagCatalogue()
+      this.tagData={
+        tId:row?row.tId:0,
+        mId:row?row.mId:0,
+        tName:row?row.tName:''
+      }
+      this.tagDiaShow=true
+    },
+    tagSuccess({type,menuIds}){
+      if(type == 'add' && menuIds && menuIds.length>0){
+        menuIds.map(code =>{
+          if (!this.tagDefaultShowNodes.some((item) => item === code)) {
+            this.tagDefaultShowNodes.push(code)
+          }
+        })
+      }
+      this.getTagTreeData()
+    },
+    searchLoad() {
+      if(!this.searchHaveMore) return;
+      this.searchPage++
+			this.searchApi(this.current_search);
+    },
+    searchHandle(query) {
+      this.searchPage = 1;
+			this.current_search = query;
+			this.searchApi(this.current_search)
+    },
+    inputFocusHandle(e) {
+      this.searchPage = 1;
+			this.current_search = e.target.value;
+			this.searchApi(this.current_search);
+    },
+		searchApi(query) {
+      let params={
+        CurrentIndex:this.searchPage,
+        PageSize:this.searchPageSize,
+        FileName:query
+      }
+      asrInterface.getSpeechList(params).then(res=>{
+        if(res.Ret == 200){
+          let arr = res.Data.List||[]
+          let paging = res.Data.Paging || {}
+          this.searchHaveMore = this.searchPage < paging.Pages;
+          this.searchOptions = this.searchPage==1?arr:[...this.searchOptions,...arr]
+        }
+      })
+		},
+    /* 判断节点是否能被拖拽 */
+		canDragHandle({data}) {
+      return (this.leftShowLabel=="目录" && this.permissionBtn.isShowBtn('semanticPermission','ASR_classify_catalogue_move'))
+            || (this.leftShowLabel=="标签" && this.permissionBtn.isShowBtn('semanticPermission','ASR_classify_tag_move'))
+		},
+    /* 判断节点是否能被拖入 */
+		canDropHandle(draggingNode, dropNode, type) {
+			let canDrop=false
+			let elementKey = this.leftShowLabel=="目录"?"SpeechRecognitionId":"TagId"
+			// 如果拖动的是元素(文件、标签)
+			if(draggingNode.data[elementKey]){
+				if(!(dropNode.level===1&&type!=='inner')){
+					canDrop=true
+				}
+			}else{//拖动的是目录
+				// console.log(dropNode.level,draggingNode.level);
+				//目录层级不能改变
+				if((dropNode.level+1==draggingNode.level&&type==='inner'&&!dropNode.data[elementKey])||(dropNode.level===draggingNode.level&&type!=='inner')){
+					canDrop=true
+				}
+			}
+			return canDrop
+		},
+    async getLazyTreeData(node, resolve) {
+      if(node.level===0){
+				resolve(this.treeData)
+			}else{
+				let arr=[]
+				const res=await asrInterface.getCatalogueList({ParentId:node.data.MenuId})
+				if (res.Ret === 200) {
+					const temarr = res.Data || [];
+					arr=temarr.map(item=>{
+						return {
+							...item,
+							isLeaf:item.SpeechRecognitionId?true:false
+						}
+					})
+          this.$nextTick(()=>{
+            this.$refs.treeRef.setCurrentKey(this.select_node_key);
+            this.select_node_data = this.$refs.treeRef.getCurrentNode()
+          })
+				}
+				resolve(arr)
+			}
+    },
+    async getLazyTreeTagData(node,resolve){
+      if(node.level===0){
+				resolve(this.tagTreeData)
+			}else{
+				let arr=[]
+				const res=await asrInterface.getTagCatalogueList({ParentId:node.data.MenuId})
+				if (res.Ret === 200) {
+					const temarr = res.Data || [];
+					arr=temarr.map(item=>{
+						return {
+							...item,
+							isLeaf:item.TagId?true:false
+						}
+					})
+          this.$nextTick(()=>{
+            this.$refs.tagTreeRef.setCurrentKey(this.tag_select_node_key);
+            this.select_node_data = this.$refs.tagTreeRef.getCurrentNode()
+          })
+				}
+				resolve(arr)
+			}
+    },
+    // 树节点展开
+    handleNodeExpand(data) {
+      // 保存当前展开的节点
+      let showNodes=[]
+      if(this.leftShowLabel=="目录"){
+        showNodes = this.defaultShowNodes
+      }else{
+        showNodes = this.tagDefaultShowNodes
+      }
+
+      let flag = showNodes.some((item) => item === data.UniqueCode);
+      if (!flag) { // 不存在则存到数组里
+        showNodes.push(data.UniqueCode)
+      }
+    },
+    // 树节点关闭
+    handleNodeCollapse(data) {
+      let showNodes=[]
+      if(this.leftShowLabel=="目录"){
+        showNodes = this.defaultShowNodes
+      }else{
+        showNodes = this.tagDefaultShowNodes
+      }
+      showNodes.some((item, index) => {
+        if (item === data.UniqueCode) {
+          // 删除关闭节点
+          showNodes.length = index;
+        }
+      });
+    },
+    /* 节点变化时 */
+    nodeChange(data, node) {
+      this.select_node_data=data
+      this.search_txt=""
+      if(data.SpeechRecognitionId){
+        // 详情
+        this.switchDetail(data)
+      }else{
+        this.clearFilterOption()
+        if(this.leftShowLabel=="目录"){
+          this.select_node_key = data.UniqueCode;
+        }else{
+          this.tag_select_node_key = data.UniqueCode;
+        }
+      }
+ 
+    },
+    /* 拖拽完成 */
+    dropOverHandle(b, a, i, e) {
+      // 被拖拽节点对应的 Node、结束拖拽时最后进入的节点、被拖拽节点的放置位置
+      // console.log(b,a,i);
+			let elementKey = this.leftShowLabel=="目录"?"SpeechRecognitionId":"TagId"
+
+			const isEDB=b.data[elementKey]?true:false
+			let list=a.parent.childNodes;
+			let targetIndex=0,PrevMenuId=0,NextMenuId=0,ParentMenuId=0;
+			let MenuId=0,ElementId=0,PrevElementId=0,NextElementId=0;
+
+			MenuId=isEDB?0:b.data.MenuId
+			ElementId=isEDB?b.data[elementKey]:0
+			
+
+			if(i!=='inner'){
+				ParentMenuId=a.parent.data.MenuId||0
+				list.forEach((item,index)=>{
+					if(isEDB){
+						if(item.data[elementKey]===b.data[elementKey]){
+							targetIndex=index
+						}
+					}else{
+						if(item.data.MenuId===b.data.MenuId){
+							targetIndex=index
+						}
+					}
+				})
+				
+				
+				if(targetIndex===0){
+					const data=list[targetIndex+1].data
+					NextMenuId=data[elementKey]?0:data.MenuId
+					NextElementId=data[elementKey]?data[elementKey]:0
+				}else if(targetIndex===list.length-1){
+					const data=list[targetIndex-1].data
+					PrevMenuId=data[elementKey]?0:data.MenuId
+					PrevElementId=data[elementKey]?data[elementKey]:0
+				}else{
+					const pData=list[targetIndex-1].data
+					PrevMenuId=pData[elementKey]?0:pData.MenuId
+
+					PrevElementId=pData[elementKey]?pData[elementKey]:0
+
+					const nData=list[targetIndex+1].data
+					NextMenuId=nData[elementKey]?0:nData.MenuId
+					NextElementId=nData[elementKey]?nData[elementKey]:0
+				}
+			}else{
+				ParentMenuId=a.data.MenuId||0
+			}
+
+      let apiName=""
+      let params=""
+      if(this.leftShowLabel=="目录"){
+        apiName="moveCatalogueEle"
+        params={
+          MenuId,
+          ParentMenuId,
+          PrevMenuId,
+          NextMenuId,
+          SpeechId:ElementId,
+          PrevSpeechId:PrevElementId,
+          NextSpeechId:NextElementId
+        }
+      }else{
+        apiName="moveTagCatalogueEle"
+        params={
+          MenuId,
+          ParentMenuId,
+          PrevMenuId,
+          NextMenuId,
+          TagId:ElementId,
+          PrevTagId:PrevElementId,
+          NextTagId:NextElementId
+        }
+      }
+			asrInterface[apiName](params).then(res=>{
+				if(res.Ret===200){
+					this.$message.success('移动成功!')
+				}
+				this.leftShowLabel=="目录"?this.getTreeData():this.getTagTreeData()
+			})
+    },
+    /* 拖拽覆盖添加背景色 */
+    dropMouseOver(node1, node2, e) {
+			// 被拖拽节点对应的 Node、所进入节点对应的 Node、event
+			if(!node2.data.EdbInfoId&&(node1.level>node2.level||(node1.data.EdbInfoId>0&&!node2.data.EdbInfoId)) && (e.target.childNodes[0].className.includes('el-tree-node__content') 
+			|| e.target.className.includes('el-tree-node__content'))) {
+				// console.log(e.target.childNodes[0])
+				e.target.childNodes[0].className.includes('el-tree-node__content') 
+				? e.target.childNodes[0].style.backgroundColor = '#409eff' 
+				: e.target.style.backgroundColor = '#409eff';
+			}
+    },
+    /* 拖拽离开/拖拽完成重置背景色 */
+    dropMouseLeave(node1, node2, e) {
+      let arrs = $('.el-tree-node__content');
+      for (let a of arrs) {
+        a.style.backgroundColor = 'transparent';
+      }
+    },
+    addAsr() {
+      this.getArticleCatalogue()
+      this.mediaUploadDiaShow=true
+    },
+    uploadSuccess(){
+      localStorage.setItem("transferStatusShow",true)
+      this.getTransferStatus()
+    },
+    getTransferStatus(){
+      let transferStatusShow = localStorage.getItem("transferStatusShow")
+      if(!!transferStatusShow){
+        asrInterface.getConvertList().then(res=>{
+          if(res.Ret == 200){
+            this.transferStatus={
+              transferingNumber:res.Data?res.Data.filter(dat => dat.State==1).length:0,
+              list:res.Data || []
+            }
+            if(this.transferStatus && this.transferStatus.transferingNumber){
+              this.transferStatusShow=true
+            }
+          }
+        })
+      }
+
+    },
+    deleteRow({item,index}){
+      this.transferStatus.list.splice(index,1)
+    },
+    handleLoadMore(){
+      if(this.articleListRequest.finished || this.articleListRequest.loading) return
+      this.page++
+      this.getArticleList()
+    },
+    resetFilterOption(type){
+      this.filterForm.author=type=='clear'?[]:this.articleParams.author
+      this.filterForm.createtimeRange=type=='clear'?[]:this.articleParams.createtimeRange
+      this.filterForm.tags=type=='clear'?[]:this.articleParams.tags
+    },
+    clearFilterOption(){
+      this.articleParams={
+        createtimeRange:[],
+        author:[],
+        tags:[]
+      }
+      this.filterForm={
+        createtimeRange:[],
+        author:[],
+        tags:[]
+      }
+    },
+    filterArticle(){
+      this.articleParams.author=this.filterForm.author
+      this.articleParams.createtimeRange=this.filterForm.createtimeRange
+      this.articleParams.tags=this.filterForm.tags
+      this.search_txt=""
+      if(this.leftShowLabel=="目录"){
+        this.select_node_key=""
+        this.$nextTick(()=>{
+          this.$refs.treeRef.setCurrentKey(null);
+        })
+      }else{
+        this.tag_select_node_key=""
+        this.$nextTick(()=>{
+          this.$refs.tagTreeRef.setCurrentKey(null);
+        })
+      }
+      this.select_node_data=""
+      this.getArticleList()
+      this.showFilterPopopver=false
+    },
+    getArticleList(){
+      if( !this.permissionBtn.isShowBtn('semanticPermission', 'ASR_view')) return 
+
+      let params={
+        CurrentIndex:this.page,
+        PageSize:this.pageSize,
+        StartTime:this.articleParams.createtimeRange?this.articleParams.createtimeRange[0]:'',
+        EndTime:this.articleParams.createtimeRange?this.articleParams.createtimeRange[1]:'',
+        CreateUserId:this.articleParams.author.join(','),
+        TagId:(this.select_node_data && this.select_node_data.TagId) ? (this.select_node_data.TagId+'') : '',
+        TagIds:this.articleParams.tags.join(','),
+        MenuId:(this.select_node_data && (!this.select_node_data.TagId)) ? this.select_node_data.MenuId : 0,
+        IsTagMenu:this.leftShowLabel=="标签"
+      }
+      asrInterface.getSpeechList(params).then(res=>{
+        if(res.Ret == 200){
+          let arr = res.Data.List||[]
+          let paging = res.Data.Paging || {}
+          this.articleList=this.page==1?arr:[...this.articleList,...arr]
+          this.total = paging.Totals || 0 
+          this.articleListRequest.finished = !(this.page < paging.Pages)
+          this.articleDetail={}
+        }
+      })
+    },
+    deleleTag(item,index){
+      this.articleDetail.Tags.splice(index,1)
+    },
+    deleteMedia(){
+      this.$confirm(`确定删除该${this.articleDetail.type==1?'音频':'视频'}吗?<span style="color:#999999;">(删除后不影响原文显示)</span>`, '提示',
+      {
+        dangerouslyUseHTMLString: true,
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        asrInterface.speechMediaRemove({SpeechRecognitionId:this.articleDetail.SpeechRecognitionId}).then(res=>{
+          if(res.Ret == 200){
+            this.articleDetail.ResourceUrl=""
+            this.$message.success("删除成功")
+          }
+        })
+      }).catch(() => {})
+    },
+    handleCommandMore(e,row){
+      if(e=='del'){
+        this.articleDel(row)
+      }else if(e=='rename'){
+        this.renameDiaOpen(row)
+      }else if(e=='export'){
+        this.exportDiaOpen(row)
+      }else if(e=='tag'){
+        this.makeTagsDiaOpen(row)
+      }
+    },
+    articleDel(row){
+      this.$confirm('确定删除选中的记录吗?', '提示',
+        {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        asrInterface.speechRemove({SpeechRecognitionId:row.SpeechRecognitionId}).then(res=>{
+          if(res.Ret == 200){
+            this.$message.success("删除成功")
+            this.page=1
+            this.getArticleList()
+            this.leftShowLabel=="目录" && this.getTreeData()
+          }
+        })
+      }).catch(() => {})
+    },
+    renameDiaOpen(row){
+      this.currentRow=row
+      this.renameData={
+        id:row.SpeechRecognitionId,
+        name:row.FileName
+      }
+      this.renameDiaShow=true
+    },
+    rename(){
+      this.currentRow.FileName = this.renameData.name
+      this.leftShowLabel=="目录" && this.getTreeData()
+    },
+    exportDiaOpen(row){
+      this.exportData={
+        id:row.SpeechRecognitionId,
+        type:2,
+        shows:'timestamp'
+      }
+      this.exportDiaShow=true
+    },
+    makeTagsDiaOpen(row){
+      this.currentRow=row || this.articleDetail
+      // console.log(this.currentRow,'this.currentRow');
+      this.getTagsList()
+      this.getTagCatalogue()
+      this.makeTagsData={
+        id:this.currentRow.SpeechRecognitionId,
+        tagsArr:this.currentRow.Tags?this.currentRow.Tags.map(item => item.TagId):[],
+      }
+      this.makeTagsDiaShow=true
+    },
+    makeTagsSuccess(data){
+      this.currentRow.Tags= data || []
+    },
+    addTagSuccess(){
+      this.getTagsList()
+    },
+    updateTagsTree(codeSet){
+      let arr = [...codeSet]
+      arr.map(code =>{
+        if(code && (!this.tagDefaultShowNodes.some(item => item === code))){
+          this.tagDefaultShowNodes.push(code)
+        }
+      })
+      this.leftShowLabel == "标签" && this.getTagTreeData() 
+    },
+    switchDetail(item){
+      if(this.detailLoadig || (!this.permissionBtn.isShowBtn('semanticPermission', 'ASR_view')) ) return 
+      this.detailLoadig=true
+      asrInterface.getSpeechDetail({SpeechRecognitionId:item.SpeechRecognitionId}).then(res=>{
+        if(res.Ret == 200){
+          this.resetDetailVariate()
+          this.articleDetail=res.Data || {}
+          this.articleDetail.type = ['mp4','flv','3gp'].includes(this.articleDetail.FileExt.toLocaleLowerCase())?2:1
+
+          this.locationDetail(this.articleDetail.MenuPath)
+          this.paragraphs=this.articleDetail.Contents.map(cont => {
+            return {
+              SpeechRecognitionContentId:cont.SpeechRecognitionContentId,
+              Content:cont.Content
+            }
+          })
+          if(this.articleDetail.ResourceUrl){
+            this.$refs.mediaRef && this.$refs.mediaRef.resetPlayerParams()
+            clearInterval(this.mediaTimer)
+            let paragraphInfoLength = this.articleDetail.Contents.length
+            this.mediaTimer= setInterval(() => {
+              if(this.$refs.mediaRef && this.$refs.mediaRef.currentTime!=0){
+                // 最后一个
+                if(!( (this.articleDetail.Contents[paragraphInfoLength-1].StartMs / 1000) > this.$refs.mediaRef.currentTime)){
+                  return this.paragraphIndex=paragraphInfoLength
+                }
+
+                let i = this.articleDetail.Contents.findIndex(pa => (pa.EndMs/1000) > this.$refs.mediaRef.currentTime)
+                let it = this.articleDetail.Contents.find(pa => (pa.EndMs/1000) > this.$refs.mediaRef.currentTime)
+                this.paragraphIndex=i+1
+              }else{
+                this.paragraphIndex=0
+              }
+            }, 300);
+          }
+        }
+      }).finally(()=>{
+        this.detailLoadig=false
+      })
+    },
+    resetDetailVariate(){
+      this.keyWord=""
+      this.isCheckAll=false
+      this.isIndeterminate=false
+      this.checkList=[]
+      this.paragraphIndex=0
+      this.$refs.contentMain && $(this.$refs.contentMain).animate({
+        scrollTop:0
+      },0)
+    },
+    locationDetail(menuPath=[]){
+      this.clearFilterOption()
+      if(this.leftShowLabel=="目录"){
+        this.select_node_key = this.articleDetail.UniqueCode
+        this.$nextTick(()=>{
+          this.$refs.treeRef.setCurrentKey(this.select_node_key);
+        })
+        menuPath.map(mp =>{
+          if(mp.UniqueCode && (!this.defaultShowNodes.some(item => item === mp.UniqueCode))){
+              this.defaultShowNodes.push(mp.UniqueCode)
+            }
+        })
+      }else{
+        this.tag_select_node_key=""
+        this.$nextTick(()=>{
+          this.$refs.tagTreeRef.setCurrentKey(null);
+        })
+      }
+    },
+    checkAll(value){
+      this.isIndeterminate=false
+      if(value){
+        this.checkList=this.articleDetail.Contents.map(it => it.SpeechRecognitionContentId)
+      }else{
+        this.checkList=[]
+      }
+    },
+    checkSingle(value){
+      if(!(this.checkList && this.checkList.length)){
+        this.isCheckAll=false
+        this.isIndeterminate=false
+      }else if(this.checkList.length < this.articleDetail.Contents.length){
+        this.isCheckAll=false
+        this.isIndeterminate=true
+      }else{
+        this.isCheckAll=true
+        this.isIndeterminate=true  
+      }
+    },
+    async copyText(item){
+      let text = ''
+      if(item){
+        text=item.Content
+      }else{
+        // 复制勾选的所有复选框文本
+        let cloneList = this.checkList.map(i => i)
+        for (let i = 0; i < this.articleDetail.Contents.length; i++) {
+          const element = this.articleDetail.Contents[i];
+          for (let j = 0; j < cloneList.length; j++) {
+            const check = cloneList[j];
+            if(check == element.SpeechRecognitionContentId){
+              text+='\n'+element.Content
+              cloneList.splice(j,1)
+              break
+            }
+          }
+        }
+        text=text.substring(2)
+      }
+      if(window.ClipboardItem) {
+        await navigator.clipboard.writeText(text).then(
+        () => {
+            this.$message.success('复制成功!')
+        },
+        (err) => {
+            console.error(err);
+            this.$message.warning('浏览器不支持')
+        })
+      }else {
+        this.$message.warning('当前协议暂不支持,仅支持https协议')
+      }	
+    },
+    toggleTimestampShow(item){
+      if(item){
+        item.HideTimestamp=!item.HideTimestamp
+      }else{
+        this.hideTimestampAll=!this.hideTimestampAll
+        let cloneList = this.checkList.map(i => i)
+        for (let i = 0; i < this.articleDetail.Contents.length; i++) {
+          const element = this.articleDetail.Contents[i];
+          for (let j = 0; j < cloneList.length; j++) {
+            const check = cloneList[j];
+            if(check == element.SpeechRecognitionContentId){
+              element.HideTimestamp=this.hideTimestampAll
+              cloneList.splice(j,1)
+              break
+            }
+          }
+        }
+      }
+    },
+    searchKeyword:_.debounce(function(value){
+      this.num=0
+      this.paragraphIndex=0
+      this.wordList=[]
+      document.querySelectorAll('.search-active').forEach(item=>{
+        item.parentNode && (item.parentNode.innerHTML = item.parentNode.innerText)
+      })
+
+      if(!value) return 
+
+      this.setHighlight()
+    },300),
+    setHighlight(){
+        //高亮
+        const blockContent = document.querySelectorAll('.content-row-text')
+        let allTextNodes = []
+        blockContent.forEach((item,index)=>{
+          allTextNodes = allTextNodes.concat(this.getTextNodes(item,index))
+        })
+
+        this.getHighlightWordRange(allTextNodes,this.keyWord)
+
+        if(this.wordList.length==0){
+          return 
+        }
+        this.wordList.forEach((item,index) =>{
+          let n = document.createElement("SPAN");
+          n.className="search-active"
+          if(index==0){
+            n.style.backgroundColor="#CFC2FE"
+            n.style.borderBottom="none"
+          }else{
+            n.style.backgroundColor="unset"
+            n.style.borderBottom="1px solid #666666"
+          }
+          item.range.surroundContents(n);
+        })
+        this.highlightDom=$(".search-active")
+    },
+    getTextNodes (dom,i){
+      let subIndex = 0
+      const treeWalker = document.createTreeWalker(dom, NodeFilter.SHOW_TEXT);
+      const allTextNodes = [];
+      let currentNode = treeWalker.nextNode();
+      while (currentNode) {
+        allTextNodes.push({f:i,s:subIndex,node:currentNode});
+
+        if(currentNode.parentElement.className == "result-content-word"){
+          subIndex++
+        }
+        currentNode = treeWalker.nextNode();
+      }
+      return allTextNodes
+    },
+    // 高亮
+    getHighlightWordRange(allTextNodes, searchText){
+      let textLength = searchText.length
+      const str = searchText.toLowerCase()
+      for (let i = 0; i < allTextNodes.length;i++) {
+        let searchPosition=0
+        const element = allTextNodes[i].node
+        let text = element.textContent.toLowerCase()
+        let sIndex,eIndex;
+        while(true){
+            sIndex= text.indexOf(str,searchPosition)
+            if(sIndex==-1){
+              break;
+            }
+            eIndex=sIndex+textLength
+            const range = new Range();
+            range.setStart(element, sIndex);
+            range.setEnd(element, eIndex);
+            this.wordList.push({
+              index:i,
+              range,
+              startMs:this.articleDetail.Contents[i].StartMs,
+            })
+            searchPosition=eIndex
+          }
+      }
+      setTimeout(()=>{
+        this.num=this.wordList.length>0?1:0
+      })
+    },
+    last(){
+      this.highlightDom[this.num-1].style.backgroundColor="unset"
+      this.highlightDom[this.num-1].style.borderBottom="1px solid #666666"
+      if(this.num<2){
+        this.num=this.wordList.length
+      }else{
+        this.num--
+      }
+      this.highlightDom[this.num-1].style.backgroundColor="#CFC2FE"
+      this.highlightDom[this.num-1].style.borderBottom="none"
+    },
+    next(){
+      this.highlightDom[this.num-1].style.backgroundColor="unset"
+      this.highlightDom[this.num-1].style.borderBottom="1px solid #666666"
+      if(this.num==this.wordList.length){
+        this.num=1
+      }else{
+        this.num++
+      }
+      this.highlightDom[this.num-1].style.backgroundColor="#CFC2FE"
+      this.highlightDom[this.num-1].style.borderBottom="none"
+    },
+    updateData(e,row,index){
+      // 这种方法在有关键词高亮的段落会出问题,因高亮改变了html结构 有解决方案请替换
+      // row.Content=e.target.textContent
+
+      this.paragraphs[index].Content=e.target.textContent
+    },
+    save(){this.articleDetail.Tags
+      if(this.saveLoading) return 
+      let params={
+        SpeechRecognitionId:this.articleDetail.SpeechRecognitionId,
+        FileName:this.articleDetail.FileName,
+        TagIds:this.articleDetail.Tags?this.articleDetail.Tags.map(tag => tag.TagId):[],
+        Contents:this.paragraphs
+      }
+      setTimeout(()=>{
+        this.saveLoading=true
+        asrInterface.speechSave(params).then(res=>{
+          if(res.Ret == 200){
+            this.$message.success("保存成功")
+            // this.searchKeyword("")
+            // setTimeout(()=>{
+            //   this.switchDetail(this.select_node_data)
+            // },300)
+            this.leftShowLabel=="目录" && this.getTreeData()
+          }
+        }).finally(()=>{
+          this.saveLoading=false
+        })
+      },10)
+    }
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+#asr-container {
+
+  .content-row-operation{
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    color: #999999;
+    margin-right: 20px;
+    img{
+      height: 16px;
+      width: 16px;
+    }
+    span{
+      margin-left: 4px;
+      white-space: nowrap;
+    }
+  }
+  .content-row-operation.active{
+    span{
+      color: #0052D9;
+    }
+  }
+  .content-row-operation.drange{
+    span{
+      color: #AD352F;
+    }
+  }
+  
+  .asr-main {
+    display: flex;
+
+    .asr-main-left {
+      width: 390px;
+      min-width: 340px;
+      background: #fff;
+      margin-right: 20px;
+      border: 1px solid #ececec;
+      border-radius: 4px;
+      height: calc(100vh - 112px);
+      box-sizing: border-box;
+
+      .add-zone {
+        padding: 15px 20px;
+        border: 1px solid #ECECEC;
+        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
+        border-radius: 4px 4px 0 0;
+      }
+
+      .catalogue-zone {
+        height: calc(100% - 106px);
+        padding: 15px 20px 20px;
+        position: relative;
+
+        .search-box {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          flex-wrap: nowrap;
+          margin-bottom: 20px;
+
+          .select-com {
+            flex-grow: 1;
+            margin-right: 20px;
+          }
+
+          .filter-box {
+            display: flex;
+            align-items: center;
+            white-space: nowrap;
+            cursor: pointer;
+
+            img {
+              height: 16px;
+              width: 16px;
+              margin-right: 4px;
+            }
+
+            span {
+              font-size: 14px;
+              line-height: 22px;
+              font-weight: 400;
+              color: #0052D9;
+            }
+          }
+        }
+
+        .catalogue-head {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          margin-bottom: 15px;
+
+          .left-tab {
+            display: flex;
+            align-items: center;
+
+            .tab {
+              cursor: pointer;
+              font-size: 14px;
+              border-bottom: 2px solid transparent;
+              margin-right: 30px;
+              padding-bottom: 5px;
+
+              &.act {
+                color: #0052D9;
+                border-color: #0052D9;
+              }
+
+              &:hover {
+                color: #0052D9;
+              }
+            }
+          }
+        }
+        .tree-container{
+          height:calc(100% - 100px);
+          .tree-cont {
+            overflow: auto;
+            height:calc(100% - 30px);
+            .target_tree {
+              font-size: 14px;
+              color: #333;
+
+              .custom-tree-node {
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+                flex: 1;
+                max-width: 100%;
+                overflow: hidden;
+                .text-span{
+                  overflow: hidden;
+                  white-space: nowrap;
+                  text-overflow: ellipsis;
+                }
+                .operation-span{
+                  display: flex; 
+                  align-items: center;
+                  img{
+                    height: 16px;
+                    width: 16px;
+                    margin-right: 8px;
+                  }
+                }
+              }
+            }
+          }
+          .bottom-operation{
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            position: absolute;
+            bottom: 20px;
+            left: 20px;
+            right: 20px;
+          }
+          .bottom-add {
+            display: flex;
+            align-items: center;
+            white-space: nowrap;
+            cursor: pointer;
+            img {
+              height: 14px;
+              width: 14px;
+              margin-right: 8px;
+            }
+
+            span {
+              font-size: 14px;
+              line-height: 22px;
+              font-weight: 400;
+              color: #0052D9;
+            }
+          }
+        }
+
+
+
+      }
+    }
+    .asr-main-right{
+      flex: 1;
+      background-color: white;
+      height: calc(100vh - 112px);
+      min-height: calc(100vh - 112px);
+      box-sizing: border-box;
+      padding: 25px 0 0;
+      border: 1px solid #DCDFE6;
+      border-radius: 4px;
+      min-width: 845px;
+      overflow-x: hidden;
+      .file-count{
+        font-size: 14px;
+        font-weight: 400;
+        margin-bottom: 16px;
+        padding: 0 25px;
+      }
+      .file-container{
+        padding: 0 25px;
+        // height: calc(100% - 35px);
+        overflow: auto;
+        box-sizing: border-box;
+        padding-bottom: 25px;
+        display: flex;
+        flex-wrap: wrap;
+        // justify-content: space-between;
+        // align-items: flex-start;
+        gap: 25px;
+        .file-item{
+          width: calc(33% - 15px);
+          min-width: 378px;
+          min-height: 300px;
+          box-sizing: border-box;
+          background-color:#ECF2FE ;
+          border: 1px solid #C8CDD9;
+          border-radius: 4px;
+          // margin-bottom: 25px;
+          padding-bottom: 50px;
+          position: relative;
+          cursor: pointer;
+          .file-main{
+            padding: 14px 20px;
+            .file-name{
+              font-size: 18px;
+              color: #333333;
+              font-weight: bold;
+              white-space: nowrap;
+              overflow: hidden;
+              text-overflow: ellipsis;
+            }
+            .file-tag-list{
+              display: flex;
+              align-items: center;
+              overflow: hidden;
+              margin-top: 10px;
+              white-space: nowrap;
+              min-height: 24px;
+              .file-tag{
+                // min-width: 0;
+                white-space: nowrap;
+                background-color: #FFFFFF;
+                border-radius: 4px;
+                padding: 0 12px;
+                font-size: 14px;
+                line-height: 24px;
+                font-weight: 400;
+                color: #999999;
+                margin-right: 10px;
+                &:last-child{
+                  margin-top: 0;
+                }
+              }
+            }
+            .file-content{
+              margin-top: 10px;
+              font-size: 14px;
+              font-weight: 400;
+              line-height: 22px;
+              color: #666666;
+              display: -webkit-box;
+              -webkit-line-clamp: 7;
+              -webkit-box-orient: vertical;
+              overflow: hidden;
+            }
+          }
+          .file-footer{
+            position: absolute;
+            bottom: 0;
+            width: 100%;
+            border-top: 1px solid #C8CDD9;
+            padding: 10px 20px 10px 10px;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            font-size: 14px;
+            font-weight: 400;
+            white-space: nowrap;
+            box-sizing: border-box;
+            flex-wrap: wrap;
+            gap: 0 10px;
+            .file-time{
+              display: flex;
+              align-items: center;
+              .split-element{
+                padding-left: 5px;
+                border-left: solid 1px #333333;
+                margin-left: 10px;
+              }
+            }
+            // .file-author{
+            //   margin-left: 10px;
+            // }
+            .file-operation{
+              margin-left: 10px;
+              display: none;
+              img{
+                height: 16px;
+                width: 16px;
+              }
+            }
+          }
+          &:hover{
+            border: 1px solid #0052D9;
+            box-shadow: 0 0 0 1px #0052d9 inset;
+            // margin: 0 -1px 24px -1px;
+            .file-time{
+              visibility: hidden;
+            }
+            .file-author{
+              display: none;
+            }
+            .file-operation{
+              display: block;
+            }
+          }
+        }
+      }
+      .tag-button{
+        display: flex;
+        align-items: center;
+        white-space: nowrap;
+        // margin-right: 20px;
+        background-color: #ECF2FE;
+        padding: 4px 8px;
+        img{
+          height: 16px;
+          width: 16px;
+          margin-right: 4px;
+        }
+        .tag-close-icon{
+          margin: 0 0 0 8px;
+          cursor: pointer;
+        }
+        span{
+          font-size: 14px;
+          line-height: 22px;
+          font-weight: 400;
+          color: #333333;
+        }
+      }
+      // 详情
+      .detail-top{
+        padding: 0 25px;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        margin-bottom: 20px;
+        .detail-top-search{
+          display: flex;
+          align-items: center;
+          .toggle-keywords-box{
+            display: flex;
+            align-items: center;
+            img{
+              cursor: pointer;
+              height: 16px;
+              width: 16px;
+            }
+            span{
+              font-size: 14px;
+              line-height: 22px;
+              color: #333333;
+              margin: 0 4px;
+            }
+          }
+        }
+      }
+      .detail-tag-area{
+        margin: 0 25px 10px;
+        display: flex;
+        align-items: center;
+        justify-content: flex-start;
+        flex-wrap: wrap;
+        // margin-bottom: 10px;
+        gap: 10px 20px;
+        max-height: 80px;
+        overflow: auto;
+      }
+      .detail-video-area{
+        height: 390px;
+        margin: 0 25px;
+        background-color: black;  
+        text-align: center;
+      }
+      .detail-content{
+        padding: 0 25px;
+        .deail-top-operation{
+          display: flex;
+          align-items: center;
+          justify-content: flex-end;
+          flex-wrap: wrap;
+          gap:0 20px;
+          margin: 10px 0;
+        }
+        .content-mutil-operation{
+          display: flex;
+          align-items: center;
+          margin: 20px 0;
+        }
+        .content-title{
+          font-size: 16px;
+          font-weight: 400;
+          position: relative;
+          padding-left: 10px;
+          line-height: 24px;
+          margin-bottom: 10px;
+          &::before{
+            content: '';
+            height: 16px;
+            width: 2px;
+            background-color: #0052D9;
+            position: absolute;
+            left: 0;
+            top: 4px;
+          }
+        }
+        .content-main{
+          position: relative;
+          height: calc(100vh - 425px);
+          min-height: 490px;
+          overflow-y: auto;
+          padding-top: 15px;
+          box-sizing: border-box;
+          .content-row{
+            margin-bottom: 25px;
+            font-size: 14px;
+            line-height: 22px;
+            font-weight: 400;
+            &:last-child{
+              margin-bottom: 0;
+            }
+            .content-row-info{
+              display: flex;
+              align-items: center;
+              .content-row-time{
+                display: flex;
+                align-items: center;
+                color: #333333;
+                margin-right: 20px;
+                .content-info-speaker{
+                  height: 16px;
+                  width: 16px;
+                }
+                .content-info-start{
+                  margin-left: 8px;
+                }
+              }
+ 
+            }
+            .content-row-content{
+              display: flex;
+              align-items: flex-start;
+              margin-top: 10px;
+              .content-row-text{
+                color: #666666;
+              }
+            }
+
+          }
+        }
+      }
+      .detail-audio-area{
+        padding: 15px 80px 0;
+        // margin-top: 15px;
+        height: 74px;
+        position: relative;
+        img{
+          display: none;
+          height: 24px;
+          width: 24px;
+          cursor: pointer;
+          position: absolute;
+          right: 30px;
+          top: 50%;
+          transform: translateY(-50%);
+        }
+        &:hover{
+          img{
+            display:inline-block;
+          }
+        }
+      }
+    }
+    .asr-main-right.detail{
+      min-width: 945px;
+      padding-bottom: 25px;
+      overflow-y: auto;
+    }
+  }
+  .status-window{
+    position: fixed;
+  }
+}
+
+.filter-poopver{
+  padding: 18px;
+  box-sizing: border-box;
+  .filter-poopver-header{
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    span{
+      font-size: 16px;
+      font-weight: 400;
+      line-height: 22px;
+      color: #333333;
+    }
+  }
+  .filter-poopver-form{
+    margin-top: 40px;
+  }
+  .filter-poopver-footer{
+    margin-top: 40px;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+  }
+}
+</style>
+<style lang="scss">
+.target_tree {
+  .el-tree__drop-indicator {
+    height: 3px;
+    background-color: #409eff;
+  }
+
+  .el-tree-node__content {
+    margin-bottom: 14px !important;
+  }
+
+  .el-tree-node__children {
+    .el-tree-node {
+      margin-bottom: 0px !important;
+      padding-left: 18px;
+    }
+
+    .el-tree-node__content {
+      margin-bottom: 5px !important;
+      padding-left: 0 !important;
+    }
+  }
+
+  .expanded.el-icon-caret-right:before {
+    content: url('../../../assets/img/set_m/down_black.png') !important;
+  }
+
+  .el-icon-caret-right:before {
+    content: url('../../../assets/img/set_m/slide_black.png') !important;
+  }
+  .el-tree-node__expand-icon{
+    padding-top: 10px;
+  }
+
+  .el-tree-node__expand-icon.is-leaf.el-icon-caret-right:before {
+    content: '' !important;
+  }
+
+  .el-tree-node__expand-icon.expanded {
+    -webkit-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+
+  .el-tree-node.is-current>.el-tree-node__content {
+    background-color: #f0f4ff !important;
+  }
+
+  .el-tree-node__content {
+    padding-right: 10px !important;
+  }
+}
+.nodata{
+  margin-top: 100px;
+  span{
+    color: #999999;
+    font-size: 14px;
+    font-weight: 400;
+  }
+}
+.el-input-group__append{
+  padding: 0 10px;
+}
+</style>

+ 139 - 0
src/views/semantics_manage/asr/components/catalogueDia.vue

@@ -0,0 +1,139 @@
+<template>
+	<el-dialog
+		:visible.sync="diaShow"
+		:close-on-click-modal="false"
+		:modal-append-to-body='false'
+    :title="diaTitle"
+		@close="closeHandle"
+		center
+		width="480px">
+			<div class="dialog-main">
+				<el-form
+				ref="diaForm"
+				label-position="top"
+				hide-required-asterisk
+				:model="formData">
+          <el-form-item :label="(index+1)==formData.level?'目录名称':getFormLabel(index,formData.level)" 
+          :rules="{required:true,message:'目录名称必填',trigger:'blur'}" :prop="formProps[index]" 
+          v-for="(item,index) in new Array(formData.level)" :key="index">
+            <el-input v-model="formData.first" style="width: 100%" placeholder="必填项" v-if="index==0" :disabled="formData.level!=1"></el-input>
+            <el-input v-model="formData.second" style="width: 100%" placeholder="必填项" v-else-if="index==1" :disabled="formData.level!=2"></el-input>
+            <el-input v-model="formData.third" style="width: 100%" placeholder="必填项" v-else></el-input>
+          </el-form-item>
+				</el-form>
+        <div class="dia-bot">
+          <el-button type="primary" plain style="margin-right:20px;min-width:120px ;" @click="cancelHandle">取消</el-button>
+          <el-button type="primary" @click="saveHandle" style="min-width:120px;">保存</el-button>
+        </div>
+			</div>
+		</el-dialog>
+</template>
+
+<script>
+
+import {asrInterface} from '@/api/modules/semanticsApi.js';
+
+  export default {
+    name:"catalogueDia",
+    props:{
+      formData:{
+        type:Object,
+        default:()=>{
+          return {}
+        }
+      },
+      diaShow:{
+        type:Boolean,
+        default:false
+      }
+    },
+    watch:{
+      diaShow(value){
+        if(value){
+          this.diaTitle = this.formData.code?"编辑":"添加"
+          this.$refs.diaForm && this.$nextTick(()=>{
+            this.$refs.diaForm.clearValidate()
+          })
+        }
+      }
+    },
+    data() {
+      return {
+        diaTitle:'',
+        formProps:['first','second','third']
+      }
+    },
+    methods: {
+      cancelHandle(){
+        this.$emit("update:diaShow",false)
+      },
+      saveHandle(){
+        this.$refs.diaForm.validate(valid=>{
+          if(valid){
+            // console.log(this.formData,'formData');
+            let apiName = ""
+            if(this.formData.mId){
+              if(this.formData.type == "目录"){
+                apiName="editCatalogue"
+              }else if(this.formData.type == "标签"){
+                apiName="editTagCatalogue"
+              }
+              // 编辑
+              let keyI = this.formProps[this.formData.level-1]
+
+              asrInterface[apiName]({
+                MenuId:this.formData.mId,
+                MenuName:this.formData[keyI]
+              }).then(res=>{
+                if(res.Ret == 200){
+                  this.$message.success(this.diaTitle+"成功")
+                  this.$emit("catalogueSuccess",{type:'edit',code:''})
+                  this.$emit("update:diaShow",false)
+                }
+              })
+            }else{
+
+              if(this.formData.type == "目录"){
+                apiName="addCatalogue"
+              }else if(this.formData.type == "标签"){
+                apiName="addTagCatalogue"
+              }
+              let keyI = this.formProps[this.formData.level-1]
+
+              asrInterface[apiName]({
+                ParentId:this.formData.pId,
+                MenuName:this.formData[keyI]
+              }).then(res=>{
+                if(res.Ret == 200){
+                  this.$message.success(this.diaTitle+"成功")
+                  this.$emit("catalogueSuccess",{type:'add',code:this.formData.code})
+                  this.$emit("update:diaShow",false)
+                }
+              })
+            }
+          }
+        })
+      },
+      getFormLabel(index,level){
+        if(level==2){
+          return "上级目录"
+        }
+        let levelLabel=['一级目录','二级目录']
+        return levelLabel[index]
+      },
+      closeHandle(){
+        this.$emit("update:diaShow",false)
+      }
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+.dialog-main{
+  padding: 5px 35px 35px;
+  .dia-bot{
+    text-align: center;
+    margin-top: 60px;
+  }
+}
+</style>

+ 130 - 0
src/views/semantics_manage/asr/components/editTag.vue

@@ -0,0 +1,130 @@
+<template>
+	<el-dialog
+		:visible.sync="diaShow"
+		:close-on-click-modal="false"
+		:modal-append-to-body='false'
+    :title="diaTitle"
+		@close="closeHandle"
+		center
+		width="480px">
+			<div class="dialog-main">
+				<el-form
+				ref="diaForm"
+				label-position="top"
+				hide-required-asterisk
+				:model="formData">
+          <el-form-item label="标签名称" :rules="{required:true,message:'标签名称必填',trigger:'blur'}" prop="tName">
+            <el-input v-model="formData.tName" style="width: 100%" placeholder="请输入标签名称"></el-input>
+          </el-form-item>
+          <el-form-item label="所属目录" :rules="{required:true,message:'所属目录必填',trigger:'change'}" prop="mId"
+          v-if="!formData.tId">
+            <el-cascader v-model="formData.mId" ref="editTagCascader" placeholder="请选择标签目录" clearable id="edit-tag-cascader"
+              :options="tagCatalogue" :props="{value:'MenuId',label:'MenuName',children:'Children',checkStrictly:true,emitPath:false}"
+              style="width: 100%;" :clearable="false" ></el-cascader>
+          </el-form-item>
+				</el-form>
+        <div class="dia-bot">
+          <el-button type="primary" plain style="margin-right:20px;min-width:120px ;" @click="cancelHandle">取消</el-button>
+          <el-button type="primary" @click="saveHandle" style="min-width:120px;">保存</el-button>
+        </div>
+			</div>
+		</el-dialog>
+</template>
+
+<script>
+import {asrInterface} from '@/api/modules/semanticsApi.js';
+
+  export default {
+    name:"editTag",
+    props:{
+      formData:{
+        type:Object,
+        default:()=>{
+          return {}
+        }
+      },
+      diaShow:{
+        type:Boolean,
+        default:false
+      },
+      tagCatalogue:{
+        type:Array,
+        required:true
+      }
+    },
+    watch:{
+      diaShow(value){
+        if(value){
+          this.diaTitle = this.formData.tId?"编辑标签":"添加标签"
+          this.$refs.diaForm && this.$nextTick(()=>{
+            this.$refs.diaForm.clearValidate()
+          })
+        }
+      }
+    },
+    data() {
+      return {
+        diaTitle:'',
+      }
+    },
+    methods: {
+      cancelHandle(){
+        this.$emit("update:diaShow",false)
+      },
+      saveHandle(){
+        this.$refs.diaForm.validate(valid=>{
+          if(valid){
+            if(this.formData.tId){
+              // 编辑
+              asrInterface.editTag({
+                TagId:this.formData.tId,
+                TagName:this.formData.tName
+              }).then(res=>{
+                if(res.Ret == 200){
+                  this.$message.success(this.diaTitle+"成功")
+                  this.$emit("tagSuccess",{type:"edit"})
+                  this.$emit("update:diaShow",false)
+                }
+              })
+            }else{
+              asrInterface.addTag({
+                MenuId:this.formData.mId,
+                TagName:this.formData.tName
+              }).then(res=>{
+                if(res.Ret == 200){
+                  this.$message.success(this.diaTitle+"成功")
+                  let menuIds = this.$refs.editTagCascader.getCheckedNodes()[0] && this.$refs.editTagCascader.getCheckedNodes()[0].pathNodes.map(node=> node.data.UniqueCode)
+                  this.$emit("tagSuccess",{type:"add",menuIds})
+                  this.$emit("update:diaShow",false)
+                }
+              })
+            }
+          }
+        })
+      },
+      closeHandle(){
+        this.$emit("update:diaShow",false)
+      }
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+.dialog-main{
+  padding: 5px 35px 35px;
+  .dia-bot{
+    text-align: center;
+    margin-top: 60px;
+  }
+}
+</style>
+<style lang="scss">
+  .el-cascader-node{
+    max-width: 200px;
+  }
+#edit-tag-cascader{
+  .el-input{
+    width: 100%;
+  }
+}
+</style>

+ 117 - 0
src/views/semantics_manage/asr/components/exportDia.vue

@@ -0,0 +1,117 @@
+<template>
+	<el-dialog
+		:visible.sync="diaShow"
+		:close-on-click-modal="false"
+		:modal-append-to-body='false'
+    :title="diaTitle"
+		@close="closeHandle"
+		center
+		width="480px">
+			<div class="dialog-main">
+				<el-form
+				ref="diaForm"
+				label-position="top"
+				hide-required-asterisk
+				:model="formData">
+          <el-form-item label="文件格式" :rules="{required:true,message:'文件格式必填',trigger:'change'}" prop="type">
+            <el-select v-model="formData.type" placeholder="请选择文件格式" style="width: 100%">
+              <el-option :label="item.label" :value="item.value" v-for="item in exportType" :key="item.value"></el-option>
+            </el-select>
+          </el-form-item>
+          <el-form-item label="显示信息" prop="shows">
+            <el-select v-model="formData.shows" placeholder="请选择显示信息" style="width: 100%" clearable >
+              <el-option :label="item.label" :value="item.value" v-for="item in showsList" :key="item.value"></el-option>
+            </el-select>
+          </el-form-item>
+				</el-form>
+        <div class="dia-bot">
+          <el-button type="primary" plain style="margin-right:20px;min-width:120px ;" @click="cancelHandle" >取消</el-button>
+          <el-button type="primary" @click="saveHandle" style="min-width:120px;" >保存</el-button>
+        </div>
+			</div>
+		</el-dialog>
+</template>
+
+<script>
+
+  export default {
+    name:"exportDia",
+    props:{
+      formData:{
+        type:Object,
+        default:()=>{
+          return {}
+        }
+      },
+      diaShow:{
+        type:Boolean,
+        default:false
+      }
+    },
+    data() {
+      return {
+        diaTitle:'导出内容',
+        exportType:[{value:1,label:".txt"},{value:2,label:".doc"},{value:3,label:".pdf"}],
+        showsList:[{value:"timestamp",label:"时间戳"}],
+        exportBase: process.env.VUE_APP_API_ROOT + "/speech_recognition/export", //数据导出接口
+      }
+    },
+    watch:{
+      diaShow(value){
+        if(value){
+          this.$refs.diaForm && this.$nextTick(()=>{
+            this.$refs.diaForm.clearValidate()
+          })
+        }
+      }
+    },
+    computed: {
+      exportApi() {
+        // 数据导出接口
+        let urlStr = this.exportBase;
+        // token
+        urlStr += `?${localStorage.getItem("auth") || ""}`;
+        // 语音识别Id
+        urlStr += `&SpeechRecognitionId=${this.formData.id}`;
+        // 导出类型
+        urlStr += `&ExportType=${this.formData.type}`;
+        // 显示信息
+        urlStr += `&Timestamp=${!!this.formData.shows}`;
+        return this.escapeStr(urlStr);
+      },
+    },
+    methods: {
+      cancelHandle(){
+        this.$emit("update:diaShow",false)
+      },
+      saveHandle(){
+        this.$refs.diaForm.validate(valid=>{
+          if(valid){
+            const link = document.createElement("a");
+            link.href = this.exportApi;
+            link.download = "";
+            link.click();
+            this.$emit("update:diaShow",false)
+          }
+        })
+      },
+      // 对[#,;]转义
+      escapeStr(str) {
+        return str.replace(/#/g, escape("#")).replace(/;/g, escape(";"));
+      },
+      closeHandle(){
+        this.$emit("update:diaShow",false)
+      }
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+.dialog-main{
+  padding: 5px 35px 35px;
+  .dia-bot{
+    text-align: center;
+    margin-top: 60px;
+  }
+}
+</style>

+ 91 - 0
src/views/semantics_manage/asr/components/fileRename.vue

@@ -0,0 +1,91 @@
+<template>
+	<el-dialog
+		:visible.sync="diaShow"
+		:close-on-click-modal="false"
+		:modal-append-to-body='false'
+    :title="diaTitle"
+		@close="closeHandle"
+		center
+		width="480px">
+			<div class="dialog-main">
+				<el-form
+				ref="diaForm"
+				label-position="top"
+				hide-required-asterisk
+				:model="formData">
+          <el-form-item label="文件名称" :rules="{required:true,message:'文件名称必填',trigger:'blur'}" prop="name">
+            <el-input v-model="formData.name" style="width: 100%" placeholder="请输入文件名称"></el-input>
+          </el-form-item>
+				</el-form>
+        <div class="dia-bot">
+          <el-button type="primary" plain style="margin-right:20px;min-width:120px ;" @click="cancelHandle">取消</el-button>
+          <el-button type="primary" @click="saveHandle" style="min-width:120px;">保存</el-button>
+        </div>
+			</div>
+		</el-dialog>
+</template>
+
+<script>
+import { asrInterface } from '../../../../api/modules/semanticsApi'
+
+  export default {
+    name:"fileRename",
+    props:{
+      formData:{
+        type:Object,
+        default:()=>{
+          return {}
+        }
+      },
+      diaShow:{
+        type:Boolean,
+        default:false
+      }
+    },
+    watch:{
+      diaShow(value){
+        if(value){
+          this.$refs.diaForm && this.$nextTick(()=>{
+            this.$refs.diaForm.clearValidate()
+          })
+        }
+      }
+    },
+    data() {
+      return {
+        diaTitle:'重命名',
+      }
+    },
+    methods: {
+      cancelHandle(){
+        this.$emit("update:diaShow",false)
+      },
+      saveHandle(){
+        this.$refs.diaForm.validate(valid=>{
+          if(valid){
+            asrInterface.speechRename({SpeechRecognitionId:this.formData.id,FileName:this.formData.name}).then(res=>{
+              if(res.Ret == 200){
+                this.$message.success(this.diaTitle+"成功")
+                this.$emit("renameSuccess")
+                this.$emit("update:diaShow",false)
+              }
+            })
+          }
+        })
+      },
+      closeHandle(){
+        this.$emit("update:diaShow",false)
+      }
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+.dialog-main{
+  padding: 5px 35px 35px;
+  .dia-bot{
+    text-align: center;
+    margin-top: 60px;
+  }
+}
+</style>

+ 249 - 0
src/views/semantics_manage/asr/components/makeTags.vue

@@ -0,0 +1,249 @@
+<template>
+	<el-dialog
+		:visible.sync="diaShow"
+		:close-on-click-modal="false"
+		:modal-append-to-body='false'
+    :title="diaTitle"
+		@close="closeHandle"
+		center
+		width="800px"
+    top="5vh">
+			<div class="dialog-main">
+        <div class="make-tags-header">
+          <el-checkbox :indeterminate="isIndeterminate" v-model="isCheckAll" @change="checkAllTags">全选</el-checkbox>
+          <el-input v-model="tagSeachWord" placeholder="请输入标签名称" prefix-icon="el-icon-search" clearable
+              style="width: 360px;" @input="searchTags"></el-input>
+        </div>
+        <div class="make-tags-list">
+          <el-checkbox-group v-model="formData.tagsArr" class="make-tags-group" ref="tagsGroupRef" 
+          v-if="tagsListAfterFilter && tagsListAfterFilter.length>0" @change="tagsCheckedChange">
+            <el-checkbox :label="item.TagId" v-for="item in tagsListAfterFilter" :key="item.TagId" style="margin-right: 0;"> {{ item.TagName }}</el-checkbox>
+          </el-checkbox-group>
+          <tableNoData text="暂无数据" v-else />
+        </div>
+        <div class="make-tags-add" v-permission="permissionBtn.semanticPermission.ASR_tagMark_add">
+          <div class="make-tags-add-title">添加标签</div>
+          <el-form
+          ref="tagsForm" class="tags-form"
+          label-position="top" inline
+          hide-required-asterisk
+          :model="addTagsForm">
+            <el-form-item :rules="{required:true,message:'标签名称必填',trigger:'blur'}" prop="TagName">
+              <el-input v-model="addTagsForm.TagName" placeholder="请输入标签名称" style="width: 260px"></el-input>
+            </el-form-item>
+            <el-form-item prop="MenuId"  :rules="{required:true,message:'标签目录必填',trigger:'change'}">
+              <el-cascader v-model="addTagsForm.MenuId" placeholder="请选择标签目录" ref="makeTagsCascader" id="make-tags-cascader" style="width: 260px"
+              :options="tagCatalogueList" :props="{value:'MenuId',label:'MenuName',children:'Children',checkStrictly:true,emitPath:false}"></el-cascader>
+            </el-form-item>
+            <el-button type="primary" style="width: 100px;" @click="addTag">添加</el-button>
+          </el-form>
+        </div>
+        <div class="dia-bot">
+          <el-button type="primary" plain style="margin-right:20px;min-width:120px ;" @click="cancelHandle">取消</el-button>
+          <el-button type="primary" @click="saveHandle" style="min-width:120px;">保存</el-button>
+        </div>
+			</div>
+		</el-dialog>
+</template>
+
+<script>
+import {asrInterface} from '@/api/modules/semanticsApi.js';
+  export default {
+    name:"makeTags",
+    props:{
+      formData:{
+        type:Object,
+        default:()=>{
+          return {}
+        }
+      },
+      diaShow:{
+        type:Boolean,
+        default:false
+      },
+      tagsList:{
+        type:Array,
+        required:true
+      },
+      tagCatalogueList:{
+        type:Array,
+        required:true
+      }
+    },
+    data() {
+      return {
+        diaTitle:'打标签',
+        tagSeachWord:"",
+        isIndeterminate:false,
+        isCheckAll:false,
+        tagsListAfterFilter:[],
+        addTagsForm:{
+          TagName:'',
+          MenuId:''
+        },
+        shouldExpandCode:new Set(),
+        hasAddTag:false
+      }
+    },
+    methods: {
+      checkAllTags(){
+        this.isIndeterminate=false
+        if(this.isCheckAll){
+          this.formData.tagsArr=this.tagsList.map(item => item.TagId)
+        }else{
+          this.formData.tagsArr=[]
+        }
+      },
+      cancelHandle(){
+        this.$emit("update:diaShow",false)
+      },
+      closeHandle(){
+        if(this.hasAddTag){
+          this.$emit("updateTagsTree",this.shouldExpandCode)
+        }
+        this.addTagsForm={
+          TagName:'',
+          MenuId:''
+        }
+        this.hasAddTag=false
+        this.tagSeachWord=""
+        this.$refs.tagsForm && this.$nextTick(()=>{
+          this.$refs.tagsForm.clearValidate()
+        })
+        this.$emit("update:diaShow",false)
+      },
+      searchTags(){
+        this.updateTagsList()
+      },
+      tagsCheckedChange(value){
+        if(!( (this.formData.tagsArr && this.formData.tagsArr.length>0) && (this.tagsList && this.tagsList.length>0) )){
+          this.isIndeterminate=false
+          this.isCheckAll=false
+        }else if(this.formData.tagsArr.length<this.tagsList.length){
+          this.isIndeterminate=true
+          this.isCheckAll=false
+        }else{
+          this.isIndeterminate=false
+          this.isCheckAll=true
+        }
+      },
+      updateTagsList(scrollType){
+        this.tagsListAfterFilter=this.tagsList.filter(tag => tag.TagName.indexOf(this.tagSeachWord)!=-1)
+        // switch (scrollType) {
+        //   case "top":
+            this.$nextTick(()=>{
+              this.$refs.tagsGroupRef && this.$refs.tagsGroupRef.$el.scrollTo(0,0)
+            })
+            // break;
+          // case "bottom":
+          //   this.$nextTick(()=>{
+          //     this.$refs.tagsGroupRef && this.$refs.tagsGroupRef.$el.scrollTo(0,this.$refs.tagsGroupRef.$el.scrollHeight)
+          //   })
+          //   break;
+        // }
+      },
+      addTag(){
+        this.$refs.tagsForm.validate(valid=>{
+          if(valid){
+            asrInterface.addTag(this.addTagsForm).then(res=>{
+              if(res.Ret == 200){
+                res.Data && this.formData.tagsArr.push(res.Data)
+                // 设置展开
+                this.$refs.makeTagsCascader.getCheckedNodes()[0] && 
+                this.$refs.makeTagsCascader.getCheckedNodes()[0].pathNodes.map(pNode=>{
+                  this.shouldExpandCode.add(pNode.data.UniqueCode)
+                })
+                this.hasAddTag=true
+                this.addTagsForm={
+                  TagName:'',
+                  MenuId:''
+                }
+                this.$message.success("新增标签成功")
+                this.$emit("addTagSuccess")
+              }
+            })
+          }
+        })
+      },
+      saveHandle(){
+        asrInterface.speechSaveTag({
+          SpeechRecognitionId:this.formData.id,
+          TagIds:this.formData.tagsArr
+        }).then(res=>{
+          if(res.Ret == 200){
+            this.$message.success("打标签成功")
+            let tagsData=this.tagsList.filter(tag => this.formData.tagsArr.includes(tag.TagId))
+            this.$emit("makeTagsSuccess",tagsData)
+            this.$emit("update:diaShow",false)
+          }
+        })
+      },
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+.dialog-main{
+  padding: 5px 35px 35px;
+  .make-tags-header{
+    display: flex;
+    align-items: flex-end;
+    justify-content: space-between;
+  }
+  .make-tags-list{
+    margin-top: 20px;
+    padding: 20px;
+    box-sizing: border-box;
+    background-color: #ECF2FE;
+    border: solid 1px #C8CDD9;
+    border-radius: 4px;
+    .make-tags-group{
+      display: flex;
+      flex-direction: column;
+      gap: 20px;
+      max-height: 360px;
+      overflow: auto;
+    }
+  }
+  .make-tags-add{
+    margin-top: 30px;
+    .make-tags-add-title{
+      font-size: 14px;
+      line-height: 22px;
+      font-family: 400;
+      color: #333333;
+    }
+    .tags-form{
+      margin-top: 10px;
+      display: flex;
+      align-items: flex-start;
+      justify-content: space-between;
+    }
+  }
+  .dia-bot{
+    text-align: center;
+    margin-top: 60px;
+  }
+}
+</style>
+<style lang="scss">
+  .el-cascader-node{
+    max-width: 200px;
+  }
+#make-tags-cascader{
+  .el-input{
+    width: 100%;
+  }
+}
+
+.make-tags-group{
+  .el-checkbox{
+    margin-right: 0;
+    // display: flex;
+    .el-checkbox__label{
+      word-break: break-all;
+      white-space: normal;
+    }
+  }
+}
+</style>

+ 186 - 0
src/views/semantics_manage/asr/components/mediaCom.vue

@@ -0,0 +1,186 @@
+<template>
+  <div :class="type==1?'audio-container':'video-container'">
+    <template v-if="type==1">
+      <audio :src="ResourceUrl" ref="audioUrlRef" @timeupdate="updateProgress" @ended="audioEnded"
+      @loadedmetadata="setDuration">
+        Your browser does not support the audio element.
+      </audio>
+      <img src="~@/assets/img/document_m/audio-backward.png" @click="skipTime(-15)" />
+      <img :src="isPlaying?require('@/assets/img/document_m/audio-start.png'):require('@/assets/img/document_m/audio-stop.png')" style="height: 30px;width: 30px;"
+      @click="togglePlay"/>
+
+      <img src="~@/assets/img/document_m/audio-forward.png"  @click="skipTime(15)"/>
+      <el-slider v-model="currentTime" :show-tooltip="false" class="progress-line"
+      @input="handleDragTime" :max="duration"/>
+      <div class="played-time">{{ formatTime(currentTime) }}</div>
+      <el-dropdown @command="setSpeed" class="dropdown-speed">
+        <span>倍速 {{ speed }}x</span>
+        <el-dropdown-menu slot="dropdown">
+          <el-dropdown-item :command="0.5" :class="speed==0.5?'current-speed':''">0.5</el-dropdown-item>
+          <el-dropdown-item :command="1" :class="speed==1?'current-speed':''">1</el-dropdown-item>
+          <el-dropdown-item :command="1.5" :class="speed==1.5?'current-speed':''">1.5</el-dropdown-item>
+          <el-dropdown-item :command="2" :class="speed==2?'current-speed':''">2</el-dropdown-item>
+        </el-dropdown-menu>
+      </el-dropdown>
+    </template>
+    <template v-if="type==2">
+      <video :src="ResourceUrl" controls class="detail-video" @timeupdate="updateProgress" ref="videoRef">
+        Your browser does not support the video element.
+      </video>
+    </template>
+  </div>
+</template>
+
+<script>
+  export default {
+    name:"mediaCom",
+    props:{
+      ResourceUrl:{
+        type:String,
+        required:true
+      },
+      type:{
+        type:Number,
+        required:true
+      }
+    },
+    data() {
+      return {
+        isPlaying:false,
+        speed:1,// 倍速率,默认为1
+        currentTime: 0,
+        duration: 0,
+        showHours:true
+      }
+    },
+    methods: {
+      togglePlay(){
+        const audio = this.$refs.audioUrlRef;
+        if(this.isPlaying){
+          audio.pause()
+        }else{
+          audio.play()
+        }
+        this.isPlaying=!this.isPlaying
+      },
+      updateProgress(event) {
+        const { currentTime } = event.target;
+        this.currentTime = +currentTime.toFixed(3)
+      },
+      skipTime(seconds) {
+        const audio = this.$refs.audioUrlRef;
+
+        audio.currentTime += seconds;
+        if(audio.currentTime < 0) audio.currentTime = 0;
+        if(audio.currentTime > audio.duration) audio.currentTime = audio.duration;
+      },
+      setDuration(event){
+        this.duration = event.target.duration;
+        this.showHours = Math.floor(this.duration / 3600) > 0
+      },
+      audioEnded() {
+        this.isPlaying = false; // 音频结束时更新播放状态
+      },
+      setSpeed(s){
+        const audio = this.$refs.audioUrlRef;
+        this.speed=s
+
+        audio.playbackRate = this.speed;
+      },
+      handleDragTime(e){
+        const audio = this.$refs.audioUrlRef;
+        audio.currentTime = e
+      },
+      formatTime(timeInSeconds) {
+        
+        const hours = Math.floor(timeInSeconds / 3600);
+        const minutes = Math.floor((timeInSeconds % 3600) / 60);
+        const seconds = Math.floor(timeInSeconds % 60);
+        const result =
+        this.showHours
+        ? `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`
+        : `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+
+        return result;
+      },
+      // 重置音频的播放器状态参数
+      resetPlayerParams(){
+        this.speed=1
+        this.isPlaying=false
+      },
+      updateCurrentTime(time){
+        this.currentTime = time
+        this.$refs.videoRef && (this.$refs.videoRef.currentTime=this.currentTime)
+      }
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+.audio-container{
+  height: 100%;
+  width: 100%;
+  background-color:#ECF2FE;
+  border: 1px solid #0052D9;
+  box-sizing: border-box;
+  display: flex;
+  align-items: center;
+  padding: 0 30px;
+  img{
+    height: 20px;
+    width: 20px;
+    margin-right: 20px;
+    cursor: pointer;
+  }
+  .progress-line{
+    flex-grow: 1;
+    margin-right: 20px;
+  }
+  .played-time{
+    margin-right: 40px;
+    font-size: 14px;
+    color: #333333;
+    font-weight: 400;
+    line-height: 22px;
+  }
+  .dropdown-speed{
+    span{
+      font-size: 14px;
+      color: #0052D9;
+      font-weight: 400;
+      line-height: 22px;
+      cursor: pointer;
+    }
+  }
+
+}
+.video-container{
+  height: 100%;
+  border-radius: 4px;
+  .detail-video{
+    height: 100%;
+  }
+}
+</style>
+<style lang="scss">
+  .current-speed{
+    color: #0052D9;
+    background-color: #e6eefb;
+  }
+  .el-slider__runway{
+    background-color: white;
+    height: 3px;
+    .el-slider__bar{
+      height: 3px;
+    }
+    .el-slider__button-wrapper{
+      height: 18px;
+      width: 18px;
+      transform: translate(-50%,calc(50% - 2px));
+      .el-slider__button{
+        height: 8px;
+        width: 8px;
+      }
+    }
+  }
+</style>

+ 438 - 0
src/views/semantics_manage/asr/components/mediaUpload.vue

@@ -0,0 +1,438 @@
+<template>
+  <div>
+	<el-dialog
+		:visible.sync="diaShow"
+		:close-on-click-modal="false"
+		:modal-append-to-body='false'
+    :title="diaTitle"
+		@close="closeHandle"
+		center top="10vh"
+		width="800px">
+			<div class="dialog-main">
+				<el-form
+				ref="diaForm"
+				label-position="left"
+				hide-required-asterisk
+				:model="formData">
+          <el-form-item label="所属目录" :rules="{required:true,message:'所属目录必填',trigger:'change'}" 
+          prop="catalogue" label-width="76px">
+            <el-cascader v-model="formData.catalogue" placeholder="请选择所属目录" clearable id="media-upload-cascader"
+              :options="articleCatalogue" :props="{value:'MenuId',label:'MenuName',children:'Children',checkStrictly:true,emitPath:false}"
+              style="width: 604px;"></el-cascader>
+          </el-form-item>
+				</el-form>
+        <div class="file-upload-zone">
+          <div class="file-upload-title">
+            <span>文件数量:{{ fileList.length }} / {{ this.fileLimit }}</span>
+            <span v-if="fileList.length>0" class="file-readdition" 
+              :class="{'disable-readdition':!( (this.fileList.length+this.uploadingFileNames.length) <fileLimit)}" @click="uploadClick">继续添加</span>
+          </div>
+          <div class="file-upload-main" id="drop-upload-zone" @dragover="uploadDragover" @drop="uploadDrop">
+            <el-table :data="fileList" id="file-list-table">
+              <el-table-column label="文件名" prop="name" show-overflow-tooltip >
+                <template slot-scope="{row}">
+                  <span class="table-span">{{ row.name }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="大小" prop="sizeText" width="120">
+                <template slot-scope="{row}" >
+                  <span class="table-span">{{ row.sizeText }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="音/视频" prop="type" width="100">
+                <template slot-scope="{row}">
+                  <span class="table-span">{{ row.type }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" prop="operation" width="60">
+                <template slot-scope="{row,$index}">
+                  <span class="table-button" @click="deleteFile($index)">删除</span>
+                </template>
+              </el-table-column>
+                  <div slot="empty" class="no-file-show" @click="uploadClick">
+                    <div class="upload-text"><span style="color:#0052D9 ;">点击/拖拽</span>本地音视频文件</div>
+                    <div class="upload-message-box">
+                      <div class="upload-message-row">支持音频格式:mp3、wav、m4a、amr、wma、aac、ogg-opus、flac</div>
+                      <div class="upload-message-row">支持视频格式:mp4、flv、3gp</div>
+                      <div class="upload-message-row">单个文件最长5小时、最大1GB,单次支持上传50个</div>
+                    </div>
+                  </div>
+            </el-table>
+          </div>
+        </div>
+        <div class="dia-bot">
+          <el-button type="primary" plain style="margin-right:20px;min-width:120px ;" @click="cancelHandle">取消</el-button>
+          <el-button type="primary" @click="saveHandle" style="min-width:120px;" :disabled="fileList.length==0">开始转写</el-button>
+        </div>
+        <!-- :http-request="handleUpload"
+          :before-upload="handleBeforeUpload" -->
+        <el-upload 
+          ref="uploadRef" id="upload-media" style="display: none;"
+          accept=".mp3,.wav,.m4a,.amr,.wma,.aac,.opus,.ogg,.flac,.mp4,.flv,.3gp"
+          action="#" multiple :limit="fileLimit"
+          :on-change="uploadMedia"
+          :show-file-list="false"
+          :auto-upload="false"
+          :disabled="!((this.fileList.length+this.uploadingFileNames.length)<fileLimit)">
+        </el-upload>
+			</div>
+		</el-dialog>
+</div>
+</template>
+
+<script>
+import {uploadFileDirect} from "@/utils/common.js"
+import {asrInterface} from '@/api/modules/semanticsApi.js';
+import MD5 from "js-md5";
+  export default {
+    name:"editTag",
+    props:{
+      diaShow:{
+        type:Boolean,
+        default:false
+      },
+      articleCatalogue:{
+        type:Array,
+        required:true
+      }
+    },
+    watch:{
+      uploadingFileNames(value){
+        if(!(value && value.length>0)){
+          if(this.uploadHint){
+            this.uploadHint.close()
+            this.uploadHint=null
+          }
+          if(this.closeDia) return 
+          this.$message.success("上传完成,请检查转写列表")
+        }
+      },
+      diaShow(value){
+        if(value){
+          this.current++
+        }
+      }
+    },
+    data() {
+      return {
+        current:0,
+        diaTitle:'上传本地音视频文件',
+        formData:{
+          catalogue:''
+        },
+        fileLimit:50,
+        fileExp:new RegExp(/\.(mp3|wav|m4a|amr|wma|aac|opus|ogg|flac|mp4|flv|3gp)$/,'i'),
+        videoExp:new RegExp(/\.(mp4|flv|3gp)$/,'i'),
+        fileList:[],
+        uploadingFileNames:[],
+        uploadHint:null,
+        closeDia:false
+      }
+    },
+    methods: {
+      uploadClick(){
+        if(!( (this.fileList.length+this.uploadingFileNames.length) < this.fileLimit)) return 
+        $('#upload-media input').trigger('click')
+      },
+      uploadDragover(event){
+        event.preventDefault(); //阻止默认行为,允许放置
+      },
+      uploadDrop(event){
+        event.preventDefault(); //阻止浏览器默认行为
+        // 获取文件的数据
+        const DataTransferItemList = event.dataTransfer.files
+        DataTransferItemList.forEach(file =>{
+          file.raw=file
+          setTimeout(()=>{
+            this.uploadMedia(file)
+          })
+        })
+      },
+      async uploadMedia(file){
+        // console.log(file,this.uploadingFileNames,'this.uploadingFileNames');
+        let fileFront=file.name.split('.')[0]
+  
+        if(this.uploadingFileNames.some(item=> item == fileFront)){
+          return this.$message.error(file.name+'同名文件正在上传,请稍后重试')
+        }
+        if(!(this.fileExp.test(file.name))){
+          return this.$message.error(file.name+'格式不受支持')
+        }
+        if(!(file.size/1024/1024 < 1024.1)){
+          return this.$message.error(file.name+'大小已超限')
+        }
+
+        let res = await this.checkDuration(file)
+        if(!res.flag){
+          return this.$message.error(res.msg);
+        }
+        
+        let audioDuration = res.duration
+
+        if(!( (this.fileList.length+this.uploadingFileNames.length) < this.fileLimit)){
+          return this.$message.error(`上传文件已超过${this.fileLimit}个,${file.name}上传失败`)
+        } 
+        // 判断于 在库的文件名是否重名
+        let flag=true
+        let checkRes = await asrInterface.speechFlieRepetitionCheck({FileName:fileFront})
+        if(checkRes.Ret == 200) flag=false
+        if(flag) return 
+
+        if(!this.uploadHint){
+          this.uploadHint = this.$message({
+            type:"info",
+            message:'上传中,请勿关闭弹窗',
+            duration:0,
+            iconClass:'el-icon-loading'
+          })
+        }
+        // 上传
+        this.uploadingFileNames.push(fileFront)
+
+        const t = new Date().getTime().toString();
+        const temName = `asr/media/${process.env.NODE_ENV}/${MD5(t)}.${
+          file.raw.type.split("/")[1]
+        }`;
+        let clientType = this.$setting.dynamicOutLinks.ObjectStorageClient ||
+                  this.$store.state.dynamicOutLinks.ObjectStorageClient ||
+                  JSON.parse(localStorage.getItem('dynamicOutLinks')).ObjectStorageClient
+        let options={}
+        if((file.size/1024/1024 > 100)){
+          // 大于100MB,分片上传
+          console.log("切片上传");
+          // 上传的配置
+          options={
+            // 阿里云
+            OSS:{
+              // 设置并发上传的分片数量。
+              parallel: 10,
+              // 设置分片大小。默认值为1 MB,最小值为100 KB。
+              partSize: 1024 * 1024 * 10, // 10MB
+            },
+            /**
+             * minIO的上传方法自动将较大的文件进行分片,不需要特殊配置
+             * 官网文档翻译文:单个对象的最大大小限制为5TB。putObject透明地将大于64MiB的对象分成多个部分上传。使用MD5SUM签名仔细验证上传的数据。
+             */
+            //S3
+            S3:{
+              partSize: 10 * 1024 * 1024, // 设置每个分片大小为10MB,默认为8MB。
+              queueSize: 10, // 并发数,默认为4。
+            }
+          }
+        }
+        let windowNum=this.current
+        uploadFileDirect(clientType,file.raw,temName,options).then(res=>{
+          if(!this.diaShow || (this.current!=windowNum)) return 
+          let item = {
+            name:fileFront,
+            fileUrl:res,
+            sizeText:this.sizeFormat(file.size),
+            size:file.size,
+            type:this.videoExp.test(res)?"视频":"音频",
+            duration:audioDuration || 0
+          }
+          this.fileList.push(item)
+        }).finally(()=>{
+          this.deleteUploadItem(fileFront)
+        })
+      },
+      sizeFormat(s){
+        let size = Number(s)
+        if(size<1024){
+          return size+'B'
+        }else if(size<(1024*1024)){
+          return Math.floor((size/1024)*100)/100 +'KB'
+        }else{
+          return Math.floor((size/1024/1024)*100)/100 +'MB'
+        }
+      },
+      deleteUploadItem(name){
+        let index = this.uploadingFileNames.findIndex(fileName=> fileName==name)
+        if(index!=-1){
+          this.uploadingFileNames.splice(index,1)
+        }
+      },
+      checkDuration(file){
+        return new Promise((resolve,reject)=>{
+          try {
+            let url = URL.createObjectURL(file.raw);
+            
+            let audioElement = new Audio(url);
+            audioElement.addEventListener('loadeddata', () => {
+              if(audioElement.duration>(60*60*5+1)){
+                resolve({flag:false,msg:file.name+'时长已超限',duration:audioElement.duration})
+              }else{
+                resolve({flag:true,msg:'',duration:audioElement.duration})
+              }
+              URL.revokeObjectURL(url);
+            });
+            setTimeout(()=>{
+              resolve({flag:false,msg:file.name+"获取不到时长,请重试"})
+            },5000)
+          } catch (error) {
+            this.$message.error(error.msg)
+            console.error(error.msg);
+            reject(error)
+          }
+        })
+      },
+      deleteFile(index){
+        this.fileList.splice(index,1)
+      },
+      cancelHandle(){
+        this.$emit("update:diaShow",false)
+      },
+      saveHandle(){
+        if(this.uploadingFileNames && this.uploadingFileNames.length>0){
+          return this.$message.error("还有音/视频未上传完成,请等待上传成功后再转写!") 
+        } 
+        let params={
+          MenuId:this.formData.catalogue,
+          Files:this.fileList.map(f =>{
+            return {
+              FileName:f.name,
+              ResourceUrl:f.fileUrl,
+              FileSecond:Math.round(f.duration),
+              FileSize:f.size
+            }
+          })
+        }
+        this.$refs.diaForm.validate(valid=>{
+          if(valid){
+            asrInterface.speechTransfer(params).then(res=>{
+              if(res.Ret == 200){
+                this.$message.success("提交转写成功")
+                this.$emit("uploadSuccess")
+                this.$emit("update:diaShow",false)
+              }
+            })
+          }
+        })
+      },
+      closeHandle(){
+        this.formData={}
+        this.closeDia=true
+        this.uploadingFileNames=[]
+        this.fileList=[]
+        this.$emit("update:diaShow",false)
+        this.$nextTick(()=>{
+          this.$refs.diaForm.clearValidate()
+          this.closeDia=false
+        })
+      }
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+  @mixin font-type {
+    font-size: 14px;
+    line-height: 22px;
+    font-weight: 400;
+    color: #333333;
+  }
+.dialog-main{
+  padding: 5px 35px 35px;
+  .file-upload-zone{
+    .file-upload-title{
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      span{
+        @include font-type
+      }
+      .file-readdition{
+        color: #0052D9;
+        cursor: pointer;
+      }
+      .disable-readdition{
+        color: #999999;
+        cursor:not-allowed;
+      }
+    }
+    .file-upload-main{
+      width: 100%;
+      height: 380px;
+      box-sizing: border-box;
+      // border: 1px solid #C8CDD9;
+      background-color: #ECF2FE;
+      border-radius: 4px;
+      margin-top: 10px;
+      .table-span{
+        @include font-type;
+      }
+      .table-button{
+        @include font-type;
+        color: #C54322;
+        cursor: pointer;
+      }
+      .no-file-show{
+        height: 332px;
+        width: 100%;
+        box-sizing: border-box;
+        padding: 130px 0 30px;
+        cursor: pointer;
+        .upload-text{
+          margin-bottom:80px;
+          @include font-type
+        }
+        .upload-message-box{
+          text-align: left;
+          padding-left: 30px;
+          .upload-message-row{
+            @include font-type;
+            color: #666666;
+          }
+        }
+      }
+    }
+  }
+  .dia-bot{
+    text-align: center;
+    margin-top: 60px;
+  }
+}
+</style>
+<style lang="scss">
+  @mixin font-type {
+    font-size: 14px;
+    line-height: 22px;
+    font-weight: 400;
+    color: #333333;
+  }
+.el-cascader-node{
+  max-width: 200px;
+}
+#media-upload-cascader{
+  .el-input{
+    width: 100%;
+  }
+}
+#file-list-table{
+  background-color: #ECF2FE;
+  border: 1px solid #C8CDD9;
+  border-radius: 4px;
+  th{
+    background-color: #ECF2FE!important;
+    border-color: #C8CDD9!important;
+    @include font-type;
+    color: #999999;
+  }
+  td{
+    border: none;
+    background-color: #ECF2FE!important;
+    padding: 10px 0;
+  }
+  .el-table__body-wrapper{
+    height: 332px;
+    overflow-y: auto;
+  }
+  .el-table__empty-block{
+    .el-table__empty-text{
+      width: 100%;
+      color: unset;
+      line-height: unset;
+    }
+  }
+}
+</style>

+ 170 - 0
src/views/semantics_manage/asr/components/transferStatus.vue

@@ -0,0 +1,170 @@
+<template>
+  <div v-if="windowShow" class="transfer-status-box" id="transfer-status-box" draggable @dragstart="dragStart"
+    :style="{'height':isFold?'40px':'unset','width':isFold?'210px':'340px',right:x+'px', bottom:y+'px'}">
+    <div class="transfer-status-header">
+      <div class="status-header-left">
+        <img src="~@/assets/img/smartReport/icon02.png" :draggable="false" />
+        <span>转写中({{ transferStatus.transferingNumber || 0 }})</span>
+      </div>
+      <div class="status-header-right">
+        <img src="~@/assets/img/icons/arrow_black_up.png" style="margin-right: 12px;" @click="isFold=!isFold"
+        :style="{'transform':isFold?'rotate(0)':'rotate(180deg)'}"  :draggable="false"/>
+        <img src="~@/assets/img/icons/close.png" @click="closeWindow"  :draggable="false"/>
+      </div>
+    </div>
+    <div class="transfer-status-body" >
+      <div class="transfer-status-row" v-for="(item,index) in transferStatus.list" :key="item.SpeechRecognitionId">
+        <div class="status-body-name">
+          <span>{{ item.FileName }}</span>
+          <span v-if="item.State==3" style="color: #AD352F;">({{ stateTextArr[item.State] }}:{{ item.ConvertRemark }})</span>
+        </div>
+        <div class="status-body-right">
+          <img v-if="item.State==3" src="~@/assets/img/icons/delete-red.png" style="cursor: pointer;" 
+          @click="deleteRow(item,index)" :draggable="false" v-permission="permissionBtn.semanticPermission.ASR_deleteArticles"/>
+          <span v-else :style="{'color':item.State==0?'#999999':'#333333'}">{{ stateTextArr[item.State] }}</span>
+        </div>
+      </div>
+    </div>
+    <img id="emptyImage" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" style="display: none;">
+  </div>
+</template>
+
+<script>
+import { asrInterface } from '../../../../api/modules/semanticsApi'
+
+  export default {
+    name:"transferStatus",
+    props:{
+      windowShow:{
+        type:Boolean,
+        default:false
+      },
+      transferStatus:{
+        type:Object,
+        required:true
+      },
+    },
+    data() {
+      return {
+        isFold:true,
+        // 拖拽相关
+        x: 20,
+        y: 20,
+        dragOffsetX: null,
+        dragOffsetY: null,
+        dragDocument:null,
+        stateTextArr:['','转写中','转写完成','转写失败']
+      }
+    },
+    mounted(){
+      this.$nextTick(()=>{
+        this.dragDocument = $('#transfer-status-box')[0]
+      })
+    },
+    methods: {
+      deleteRow(item,index){
+        asrInterface.speechRemove({SpeechRecognitionId:item.SpeechRecognitionId}).then(res=>{
+          if(res.Ret == 200){
+            this.$message.success("删除成功")
+            this.$emit("deleteRow",{item,index})
+          }
+        })
+      },
+      closeWindow(){
+        localStorage.removeItem("transferStatusShow")
+        this.$emit("update:windowShow",false)
+      },
+      dragStart(event) {
+        // 计算鼠标位置偏移量
+        this.dragOffsetX = event.clientX - event.target.getBoundingClientRect().left;
+        this.dragOffsetY = event.clientY - event.target.getBoundingClientRect().top;
+        
+        event.dataTransfer.effectAllowed='move'
+        // 去掉虚影
+        event.dataTransfer.setDragImage(emptyImage, 0, 0);
+
+        document.addEventListener('dragover',this.dragover)
+        document.addEventListener('dragend',this.dragend)
+      },
+      dragover(event){
+        // 防止默认处理
+        event.preventDefault();
+
+        // 计算新位置,并更新组件的 x 和 y 数据属性
+
+        this.x = window.innerWidth-(event.clientX - this.dragOffsetX) - this.dragDocument.clientWidth
+        this.y = window.innerHeight-(event.clientY - this.dragOffsetY) - this.dragDocument.clientHeight
+
+      },
+      dragend(event) {
+        // 防止默认处理
+        event.preventDefault();
+
+        document.removeEventListener('dragover',this.dragover)
+        document.removeEventListener('dragend',this.dragend)
+      }
+    },
+  }
+</script>
+
+<style lang="scss" scoped>
+
+@mixin font-type{
+  font-size: 14px;
+  font-weight: 400;
+  line-height: 20px;
+  color:#333333;
+}
+
+.transfer-status-box{
+  width: 340px;
+  padding: 10px 20px;
+  box-sizing: border-box;
+  background-color: white;
+  overflow: hidden;
+  border-radius: 4px;
+  border: 1px solid #C8CDD9;
+  z-index: 1000;
+  img{
+    height: 16px;
+    width: 16px;
+  }
+  span{
+    @include font-type;
+  }
+  .transfer-status-header{
+    padding-bottom: 10px;
+    border-bottom: 1px solid #C8CDD9;
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    .status-header-left{
+      display: flex;
+      align-items: center;
+      img{
+        margin-right: 4px;
+      }
+    }
+    .status-header-right{
+      img{
+        cursor: pointer;
+      }
+    }
+  }
+  .transfer-status-body{
+    margin-top: 10px;
+    max-height: 90px;
+    overflow-y: auto;
+    box-sizing: border-box;
+    .transfer-status-row{
+      margin-top: 10px;
+      display: flex;
+      align-items: flex-start;
+      justify-content: space-between;
+      // .status-body-name{
+      // }
+    }
+  }
+
+}
+</style>