src/views/ai/ElectronicManual.vue
@@ -1,48 +1,832 @@
<template>
  <div>
    电子说明书
    <a-card>
      {{answer}}
    </a-card>
    <a-input-search placeholder="input search text" enter-button @search="onSearch"/>
  <div class="page-container">
    <!--电子说明书-->
    <div class="outer-container">
      <!--左侧阅读模式缩略图区域-->
      <div class="left-spin-container">
        <a-spin :spinning="thumbnailSpinning" :delay="spinningDelayTime">
          <a-icon slot="indicator" type="loading" spin/>
        </a-spin>
        <div class="left-container">
          <div v-for="item in imgListConfig.records" :key="item.id" class="single-thumbnail-container"
               :class="[item.id===activeImageId?'single-thumbnail-active':'']" :id="'thumbnail-container-'+item.id"
               @click="activeCurrentThumbnail(item.id)">
            <!--<img src="@/assets/page/electronicManual/document.png">-->
            <div class="thumbnail-image-container">
              <a-skeleton :loading="item.loading" :avatar="{shape:'square'}" :title="false" :paragraph="false" active/>
              <img :id="'thumbnail-image-'+item.id" :data-src="getImgView(item.imgPath+item.imgEncodeName)"
                   @load="imageLoadDone(item)" :style="{opacity:item.loading?0:1}">
            </div>
            <div>-{{item.pageNumber}}-</div>
          </div>
        </div>
      </div>
      <!--右侧区域-->
      <div class="right-container">
        <div class="right-top-container">
          <div class="document-spin-container">
            <a-spin :spinning="documentSpinning" :delay="spinningDelayTime">
              <a-icon slot="indicator" type="loading" spin/>
            </a-spin>
            <!--右上文档区域-->
            <div class="document-container" @wheel="horizontalScroll">
              <div v-for="item in documentList" :key="item.id" class="single-document-container"
                   @click="activeCurrentDocument(item.id)"
                   :class="[item.id===activeDocumentId?'single-document-active':'']">
                <div><img src="@/assets/page/electronicManual/document.png"></div>
                <div class="single-document-name">
                  {{item.fileName?item.fileName.length>20?item.fileName.slice(0,20)+'..':item.fileName:'未命名说明书'}}
                </div>
              </div>
            </div>
          </div>
          <!--右上输入查询区域-->
          <div class="search-container">
            <div>您当前选择的电控系统为:xxx-xxxx-xxxxx&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;机床型号为:xxx-xx</div>
            <div class="input-container">
              <input @keydown.enter="furtherFilter" placeholder="请用一句话描述您当前遇到的问题" v-model="inputQuestion"/>
              <img src="@/assets/page/electronicManual/search.png" @click="furtherFilter"
                   :style="{cursor:!thumbnailSpinning?'pointer':'not-allowed'}">
            </div>
          </div>
        </div>
        <!--右下区域-->
        <div class="right-bottom-spin-container">
          <a-spin :spinning="largeImageSpinning" :delay="spinningDelayTime">
            <a-icon slot="indicator" type="loading" spin/>
          </a-spin>
          <!--右下阅读模式区域-->
          <div class="right-bottom-container" :class="[isFurtherFilter?'further-filter-container':'']">
            <template v-if="!isFurtherFilter">
              <div v-for="item in imgListConfig.records" :key="item.id" class="single-largeImg-container"
                   :id="'large-image-container-'+item.id">
                <a-skeleton :loading="item.loading" :avatar="{shape:'square'}" :title="false"
                            :paragraph="false" active/>
                <img :id="'large-image-'+item.id" :data-src="getImgView(item.imgPath+item.imgEncodeName)"
                     @load="imageLoadDone(item)" :style="{opacity:item.loading?0:1}">
                <!--<img src="@/assets/page/electronicManual/document.png">-->
              </div>
            </template>
            <!--右下深层过滤区域-->
            <template v-else>
              <div v-for="item in furtherFilterImgList" :key="item.id" class="single-filterImg-container"
                   @click="locateToDocument(item)">
                <!--<img src="@/assets/page/electronicManual/document.png">-->
                <a-skeleton :loading="item.loading" :avatar="{shape:'square'}" :title="false"
                            :paragraph="false" active/>
                <img :id="'filter-image-'+item.id" :data-src="getImgView(item.imgPath+item.imgEncodeName)"
                     @load="imageLoadDone(item)" :style="{opacity:item.loading?0:1}"
                >
              </div>
              <img src="@/assets/page/electronicManual/back.png" id="back-to-largeImg" @click="cancelFurtherFilter"
                   v-if="!largeImageSpinning">
            </template>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
  import { getDataAfterSearchByApi } from '@/api/AI'
  import { getPdfImgApi, getPdfDocumentApi, getFurtherFilterImgApi } from '@/api/ai'
  import { getFileAccessHttpUrl } from '@/api/manage'
  import { message } from 'ant-design-vue'
  message.config({
    maxCount: 1,
    duration: 2
  })
  export default {
    name: 'ElectronicManual',
    components: {},
    data() {
      return {
        answer:''
        rightBottomContainer: null,
        activeDocumentId: null,
        activeImageId: null,
        inputQuestion: '',
        isFurtherFilter: false,
        beforeFilterActiveDocumentId: null,
        beforeFilterActiveImageId: null,
        documentList: [],
        // documentList: [
        //   {
        //     id: 1,
        //     fileName: '机床手册1'
        //   },
        //   {
        //     id: 2,
        //     fileName: '机床手册2'
        //   },
        //   {
        //     id: 3,
        //     fileName: '机床手册3'
        //   },
        //   {
        //     id: 4,
        //     fileName: '机床手册4'
        //   },
        //   {
        //     id: 5,
        //     fileName: '机床手册5'
        //   },
        //   {
        //     id: 6,
        //     fileName: '机床手册6'
        //   },
        //   {
        //     id: 7,
        //     fileName: '机床手册7'
        //   },
        //   {
        //     id: 8,
        //     fileName: '机床手册8'
        //   },
        //   {
        //     id: 9,
        //     fileName: '机床手册9'
        //   },
        //   {
        //     id: 10,
        //     fileName: '机床手册10'
        //   }
        // ],
        imgListParams: {
          pageNo: 1,
          pageSize: 20
        },
        imgListConfig: {
          records: []
        },
        scrollToMarginConfig: {
          hasScrollToTopCount: 0,
          hasScrollToBottomCount: 0
        },
        thumbnailSpinning: false,
        documentSpinning: false,
        largeImageSpinning: false,
        spinningDelayTime: 500,
        furtherFilterImgListParams: {},
        furtherFilterImgList: []
      }
    },
    created() {
      this.getPdfDocumentByApi()
    },
    mounted() {
      this.leftContainer = document.querySelector('.left-container')
      this.rightBottomContainer = document.querySelector('.right-bottom-container')
      this.handleScrollEventSwitch(true)
    },
    methods: {
      onSearch() {
        const param = {
          'id': '683a65fd-8feb-4446-ad32-714c4785f667',
          'messages': [
            {
              'role': 'user',
              'content': '给我讲个故事?'
      /* 调用接口获取pdf文档 */
      getPdfDocumentByApi() {
        this.documentSpinning = true
        getPdfDocumentApi()
          .then(res => {
            if (res.success) {
              this.documentList = res.result
              this.documentSpinning = false
              this.activeCurrentDocument(res.result[0].id)
            }
          ],
          'stream': false,
          'max_tokens': 500
          })
      },
      /**
       * 点击文档后触发
       * @param pdfFileId 文档Id
       * @param furtherFilterImageId 深层过滤模式中点击的图片Id
       */
      activeCurrentDocument(pdfFileId, furtherFilterImageId = null, furtherFilterImagePageNumber = null) {
        if (pdfFileId === this.activeDocumentId && !furtherFilterImageId) return
        if (this.isFurtherFilter) {
          this.isFurtherFilter = false
          this.handleScrollEventSwitch(true)
        }
        getDataAfterSearchByApi()
        if (!furtherFilterImageId) this.scrollToTop()
        this.imgListConfig = {}
        this.activeDocumentId = pdfFileId
        this.imgListParams.pageNo = 1
        this.resetImgListScrollConfig()
        if (furtherFilterImagePageNumber && furtherFilterImagePageNumber > this.imgListParams.pageSize) this.computeImgListPageNo(furtherFilterImagePageNumber)
        const params = Object.assign({ pdfFileId }, this.imgListParams)
        this.thumbnailSpinning = true
        this.largeImageSpinning = true
        getPdfImgApi(params)
          .then(res => {
            if (res.success) {
              this.imgListConfig = res.result
              this.imgListConfig.records = this.imgListConfig.records.map(item => {
                return {
                  ...item,
                  loading: true
                }
              })
              this.activeImageId = res.result.records[0].id
              this.$nextTick(() => {
                this.lazyLoadImgByIntersectionObserver(res.result.records, 'thumbnail-image', 4, furtherFilterImageId)
                this.lazyLoadImgByIntersectionObserver(res.result.records, 'large-image', 1, furtherFilterImageId)
              })
              this.thumbnailSpinning = false
              this.largeImageSpinning = false
              // 如果点击的是深层过滤的图片则返回阅读模式并放大选中的图片
              if (furtherFilterImageId) this.$nextTick(() => this.activeCurrentThumbnail(furtherFilterImageId, true))
            }
          })
      },
      /**
       * 点击缩略图后触发在右侧放大缩略图
       * @param id 缩略图Id
       * @param isFromFurtherFilter 是否通过深层过滤模式触发
       */
      activeCurrentThumbnail(id, isFromFurtherFilter = false) {
        if (id === this.activeImageId && !isFromFurtherFilter) return
        this.activeImageId = id
        if (isFromFurtherFilter) this.scrollToImagePosition('thumbnail-container', true, 'left', 0)
        this.scrollToImagePosition()
      },
      computeImgListPageNo(furtherFilterImagePageNumber) {
        const integer = Math.floor(furtherFilterImagePageNumber / this.imgListParams.pageSize)
        const remainder = furtherFilterImagePageNumber % this.imgListParams.pageSize
        if (remainder !== 0) {
          // 不为整数
          this.imgListParams.pageNo = integer + 1
        } else {
          // 为整数
          this.imgListParams.pageNo = integer
        }
      },
      /* 阅读模式下的滚动触顶底刷新 */
      lazyLoadData(event) {
        if (this.thumbnailSpinning && this.largeImageSpinning) return // 加载中时取消调用
        const containerScrollTop = event.target.scrollTop
        const containerClientHeight = event.target.clientHeight
        const containerScrollHeight = event.target.scrollHeight
        if (containerScrollTop + containerClientHeight >= containerScrollHeight) {
          console.log('触底')
          this.scrollToMarginConfig.hasScrollToBottomCount++
          const params = Object.assign({ pdfFileId: this.activeDocumentId }, this.imgListParams)
          params.pageNo = params.pageNo + this.scrollToMarginConfig.hasScrollToBottomCount
          this.thumbnailSpinning = true
          this.largeImageSpinning = true
          getPdfImgApi(params)
            .then(res => {
              if (res.success) {
                const newImgList = res.result.records.map(item => {
                  return {
                    ...item,
                    loading: true
                  }
                })
                this.imgListConfig.records.push(...newImgList)
                this.$nextTick(() => {
                  this.lazyLoadImgByIntersectionObserver(newImgList, 'thumbnail-image', 4)
                  this.lazyLoadImgByIntersectionObserver(newImgList, 'large-image', 1)
                })
                this.thumbnailSpinning = false
                this.largeImageSpinning = false
              }
            })
        } else if (containerScrollTop <= 0 && this.imgListConfig.records[0].pageNumber !== 1) {
          console.log('触顶')
          this.scrollToMarginConfig.hasScrollToTopCount++
          const params = Object.assign({ pdfFileId: this.activeDocumentId }, this.imgListParams)
          params.pageNo = params.pageNo - this.scrollToMarginConfig.hasScrollToTopCount
          this.thumbnailSpinning = true
          this.largeImageSpinning = true
          getPdfImgApi(params)
            .then(res => {
              if (res.success) {
                const newImgList = res.result.records.map(item => {
                  return {
                    ...item,
                    loading: true
                  }
                })
                this.imgListConfig.records.unshift(...newImgList)
                this.$nextTick(() => {
                  this.lazyLoadImgByIntersectionObserver(newImgList, 'thumbnail-image', 4)
                  this.lazyLoadImgByIntersectionObserver(newImgList, 'large-image', 1)
                })
                event.target.scrollTo({ top: 1 }) // 解决触顶刷新数据后滚动条至最顶部问题,但不知道为什么
                this.thumbnailSpinning = false
                this.largeImageSpinning = false
              }
            })
        }
      },
      /* 开启深层过滤模式 */
      furtherFilter() {
        if (this.thumbnailSpinning) return
        if (!this.inputQuestion) {
          this.$message.error('你没有输入内容哦')
          return
        }
        this.furtherFilterImgList = []
        this.isFurtherFilter = true
        // 开启深层过滤时取消文件选中并移除滚动刷新事件
        this.beforeFilterActiveDocumentId = this.activeDocumentId
        this.activeDocumentId = null
        this.imgListConfig = {}
        this.handleScrollEventSwitch(false)
        const params = Object.assign({}, this.furtherFilterImgListParams)
        params.pdfFileId = '1823231131943862273'
        params.pdfContent = this.inputQuestion
        this.largeImageSpinning = true
        getFurtherFilterImgApi(params)
          .then(res => {
            console.log('res', res)
            this.answer = res.result
            if (res.success) {
              this.furtherFilterImgList = res.result.map(item => {
                return {
                  ...item,
                  loading: true
                }
              })
              this.largeImageSpinning = false
              this.$nextTick(() => this.lazyLoadImgByIntersectionObserver(res.result, 'filter-image', 2))
            }
          })
      },
      /* 点击深层过滤中的图片后定位至文件 */
      locateToDocument(record) {
        this.isFurtherFilter = false
        this.handleScrollEventSwitch(true)
        this.$nextTick(() => this.activeCurrentDocument(record.fileId, record.id, record.pageNumber))
      },
      /* 点击返回按钮退出深层过滤模式 */
      cancelFurtherFilter() {
        const beforeFilterActiveRecord = { fileId: this.beforeFilterActiveDocumentId, id: this.activeImageId }
        this.locateToDocument(beforeFilterActiveRecord)
        this.inputQuestion = ''
      },
      /**
       * 滚动至图片相应位置顶部
       * @param activePreId 滚动至某元素的Id前缀
       * @param isParentNodeOpenPosition 父元素是否开启定位
       * @param parentNodePreClass 父元素类名前缀
       * @param marginValue 边距值 正值为top,负值为bottom
       */
      scrollToImagePosition(activePreId = 'large-image-container', isParentNodeOpenPosition = true, parentNodePreClass = 'rightBottom', marginValue = 25) {
        console.log(activePreId + '-' + this.activeImageId)
        const activeLargeImageContainer = document.getElementById(activePreId + '-' + this.activeImageId)
        console.log('activeLargeImageContainer', activeLargeImageContainer)
        let scrollTop
        if (isParentNodeOpenPosition) {
          console.log('父元素开启定位')
          scrollTop = activeLargeImageContainer.offsetTop - marginValue
        } else {
          console.log('父元素未开启定位')
          scrollTop = activeLargeImageContainer.offsetTop - this[parentNodePreClass + 'Container'].offsetTop - marginValue
        }
        this[parentNodePreClass + 'Container'].scrollTo({ top: scrollTop })
      },
      /**
       * 采用浏览器监测者API实现图片懒加载
       * @param dataList 元素对应数据集合用来映射对应元素
       * @param targetPreClass 目标元素类名前缀
       * @param loadCount 加载数量
       * @param furtherFilterImageId 深层过滤模式中点击的图片Id
       */
      lazyLoadImgByIntersectionObserver(dataList, targetPreClass, loadCount, furtherFilterImageId = null) {
        let hasLoadImageCount = 0
        let observer = new IntersectionObserver(entries => {
          const furtherFilterImageIndex = entries.findIndex(item => item.target.id === targetPreClass + '-' + furtherFilterImageId)
          // 深层过滤选中图片后减少数组循环次数,仅加载包含选中图片的可视图片数量
          if (furtherFilterImageIndex > 0) entries = entries.slice(furtherFilterImageIndex)
          entries = entries.slice(0, loadCount)
          entries.forEach(item => {
            if (item.isIntersecting) {
              item.target.src = item.target.dataset.src
              hasLoadImageCount++
              observer.unobserve(item.target)
            }
          })
          if (hasLoadImageCount === dataList.length) {
            observer.disconnect()
            observer = null
          }
        })
        dataList.forEach(item => observer.observe(document.getElementById(targetPreClass + '-' + item.id)))
      },
      horizontalScroll(event) {
        event.preventDefault()
        const documentContainer = document.querySelector('.document-container')
        // deltaY属性是鼠标滚动滚轮滚动一下,页面滚动的距离
        // Y是垂直方向
        // 鼠标滚轮向前滚动一轮,deltaY=-100;向后滚动一轮,deltaY=100;
        // deltaY的内部实现肯定是监听鼠标滚轮是向前滚动还是向后滚动事件,然后更改deltaY的值为正或者负。
        // 水平滚动也需要有类似deltaY这样的状态,所以直接使用deltaY就可以了,虽然Y表示的是垂直方向的滚动。
        const deltaY = event.deltaY
        // scrollLeft属性是元素左侧的滚动距离,通过改变这个属性的值,实现水平滚动的效果。
        documentContainer.scrollLeft += deltaY
      },
      imageLoadDone(record) {
        console.log('图片加载完成')
        record.loading = false
      },
      scrollToTop() {
        console.log('触发回到顶部')
        this.leftContainer.scrollTo({ top: 0 })
        this.rightBottomContainer.scrollTo({ top: 0 })
      },
      /**
       * 图片预览
       * @param text 图片地址
       */
      getImgView(text) {
        if (text && text.indexOf(',') > 0) {
          text = text.substring(0, text.indexOf(','))
        }
        return getFileAccessHttpUrl(text)
      },
      handleScrollEventSwitch(isAddEvent) {
        if (isAddEvent) {
          this.leftContainer.addEventListener('scroll', this.lazyLoadData)
          this.rightBottomContainer.addEventListener('scroll', this.lazyLoadData)
        } else {
          this.leftContainer.removeEventListener('scroll', this.lazyLoadData)
          this.rightBottomContainer.removeEventListener('scroll', this.lazyLoadData)
        }
      },
      resetImgListScrollConfig() {
        this.scrollToMarginConfig.hasScrollToTopCount = 0
        this.scrollToMarginConfig.hasScrollToBottomCount = 0
      }
    },
    beforeDestroy() {
      this.handleScrollEventSwitch(false)
    }
  }
</script>
<style scoped>
<style scoped lang="less">
  @main-container-background: rgba(255, 255, 255, .7);
  @container-border-radius: 12px;
  @container-padding: 10px;
  @single-history-edit-border: 3px solid #ABC0CC;
  @single-history-hover-background: #f1f1f1;
  @single-history-active-background: #e5ebed;
  @input-container-border: 3px solid #B8CAD5;
  @user-question-background: #e5ebed;
  @assistant-answer-background: #F0F5F5;
  @conversation-content-container-box-shadow: 2px 2px 10px 0px #ddd;
  @input-container-box-shadow: 2px 2px 10px 0px #ABC0CC;
  @largeImg-container-box-shadow: 0 -3px 10px 0px #ddd;
  @scrollbar-track-background: #f6f6f6;
  @scrollbar-thumb-background: #e9e9e9;
  @scrollbar-thumb-hover-background: #cfcfcf;
  @scrollbar-corner-background: #f6f6f6;
  .page-container {
    display: flex;
    justify-content: flex-end;
    -webkit-justify-content: flex-end;
    flex-direction: column;
    align-items: center;
    font-size: 18px;
    height: 100%;
    font-family: ali_r_main;
    .outer-container {
      width: 100%;
      height: 850px;
      display: flex;
      justify-content: center;
      align-items: center;
      .left-spin-container {
        width: 10%;
        height: 100%;
        background-color: @main-container-background;
        border-radius: @container-border-radius;
        margin-right: 25px;
        font-size: 16px;
        position: relative;
        .left-container {
          width: 100%;
          height: 100%;
          padding: @container-padding;
          display: flex;
          flex-direction: column;
          justify-content: space-between;
          overflow: auto;
          &::-webkit-scrollbar {
            width: 8px;
            height: 0;
          }
          .single-thumbnail-container {
            padding: @container-padding;
            cursor: pointer;
            border-radius: @container-border-radius;
            &:not(:last-child) {
              margin-bottom: 20px;
            }
            img {
              width: 100%;
              height: 189px;
            }
            & > div:last-child {
              margin-top: 10px;
              text-align: center;
            }
            .thumbnail-image-container {
              position: relative;
            }
            &:hover {
              background-color: @single-history-hover-background;
            }
            &.single-thumbnail-active {
              background-color: @single-history-active-background;
            }
            /deep/ .ant-skeleton {
              margin: 0;
              position: absolute;
              width: 100%;
              height: 189px;
              .ant-skeleton-header {
                padding: 0;
                .ant-skeleton-avatar-lg {
                  width: 100%;
                  height: 189px;
                  border-radius: @container-border-radius;
                }
              }
            }
          }
        }
      }
      .right-container {
        width: 80%;
        height: 100%;
        border-radius: @container-border-radius;
        display: flex;
        flex-direction: column;
        justify-content: space-between;
        .right-top-container {
          height: 120px;
          margin-bottom: 25px;
          display: flex;
          justify-content: center;
          .document-spin-container {
            width: 50%;
            height: 100%;
            font-size: 14px;
            background-color: @main-container-background;
            border-radius: @container-border-radius;
            margin-right: 25px;
            position: relative;
            .document-container {
              overflow-y: hidden;
              overflow-x: auto;
              display: flex;
              align-items: center;
              padding: @container-padding;
              &::-webkit-scrollbar {
                width: 0;
                height: 8px;
              }
              .single-document-container {
                width: 22%;
                height: 100%;
                cursor: pointer;
                display: flex;
                flex-direction: column;
                justify-content: space-evenly;
                align-items: center;
                flex-shrink: 0;
                border-radius: @container-border-radius;
                padding: @container-padding;
                img {
                  width: 40px;
                }
                .single-document-name {
                  width: 100%;
                  text-align: center;
                  /*overflow: hidden;*/
                  /*white-space: nowrap;*/
                  /*text-overflow: ellipsis;*/
                }
                &:not(:last-child) {
                  margin-right: 4%;
                }
                &:hover {
                  background-color: @single-history-hover-background;
                }
                &.single-document-active {
                  background-color: @single-history-active-background;
                }
              }
            }
          }
          .search-container {
            flex: 1;
            display: flex;
            flex-direction: column;
            justify-content: space-evenly;
            align-items: center;
            .input-container {
              width: 100%;
              display: flex;
              background-color: transparent;
              border: @single-history-edit-border;
              border-radius: @container-border-radius;
              padding: @container-padding;
              input {
                flex: 1;
                outline: none;
                background-color: transparent;
                letter-spacing: 1px;
                border: none;
                &::placeholder {
                  color: #9A9C9C;
                }
              }
              img {
                width: 32px;
                cursor: pointer;
              }
            }
          }
        }
        .right-bottom-spin-container {
          width: 100%;
          flex: 1;
          background-color: @main-container-background;
          border-radius: @container-border-radius;
          position: relative;
          overflow: hidden;
          .right-bottom-container {
            height: 100%;
            background-color: @main-container-background;
            border-radius: @container-border-radius;
            overflow: auto;
            text-align: center;
            display: flex;
            flex-direction: column;
            align-items: center;
            .single-largeImg-container {
              box-shadow: @largeImg-container-box-shadow;
              width: 70%;
              margin-top: 25px;
              position: relative;
            }
            &.further-filter-container {
              flex-direction: row;
              justify-content: left;
              flex-wrap: wrap;
              padding: 12px 0;
              .single-filterImg-container {
                box-shadow: @largeImg-container-box-shadow;
                width: 45%;
                margin-top: 12px;
                margin-bottom: 12px;
                cursor: pointer;
                position: relative;
                &:nth-child(odd) {
                  margin-left: 2%;
                  margin-right: 2%;
                }
              }
            }
            /deep/ .ant-skeleton {
              position: absolute;
              top: 0;
              left: 0;
              right: 0;
              bottom: 0;
              margin: 0;
              .ant-skeleton-header {
                padding: 0;
                .ant-skeleton-avatar-lg {
                  width: 100%;
                  height: 1640px;
                }
              }
            }
            img {
              width: 100%;
              height: 1640px;
            }
            #back-to-largeImg {
              width: 3%;
              height: auto;
              position: absolute;
              right: 2%;
              top: auto;
              cursor: pointer;
            }
            &::-webkit-scrollbar {
              width: 8px;
              height: 0;
            }
          }
        }
      }
      .left-container, .document-container, .right-bottom-container {
        &:hover {
          &::-webkit-scrollbar-track {
            background: @scrollbar-track-background;
          }
          &::-webkit-scrollbar-thumb {
            background: @scrollbar-thumb-background;
          }
          &::-webkit-scrollbar-thumb:hover {
            background: @scrollbar-thumb-hover-background;
          }
          &::-webkit-scrollbar-corner {
            background: @scrollbar-corner-background;
          }
        }
      }
      /deep/ .ant-spin {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        display: flex;
        justify-content: center;
        align-items: center;
        pointer-events: none;
        z-index: 999;
        .anticon {
          font-size: 24px;
        }
      }
    }
  }
</style>