【vue3】实现简易的 “百度网盘” 文件夹的组件封装实现

一、图例

二、主界面文件

 

1、 主文件 index.vue 增删改查等功能

<!-- 收藏目录 -->
<template>
  <div class="coll_pop" v-if="!person.isCloseColl">
    <div class="coll_box">
      <div class="coll_top flex">
        <div>+加入资源篮</div>
        <el-icon @click="person.isCloseColl=!person.isCloseColl"><Close /></el-icon>
      </div>
      <div class="coll_center">
        <div class="coll_title flex">
          <div class="coll_title_back" @click="backPrev">返回上一级 </div>
          <div class="coll_title_nav flex">
            <el-breadcrumb :separator-icon="ArrowRight" separator="/">
              <el-breadcrumb-item v-for="(nav,navIndex) in person.navList" :key="navIndex" @click="navChange(nav,navIndex)">
                <a href="#">{{nav.name}}</a>
              </el-breadcrumb-item>
            </el-breadcrumb>
          </div>
        </div>
      </div>
      <div class="coll_cont">
        <div class="cont_scroll">
          <div class="cont_list" v-if="person.collList&&person.collList.length>0">
            <RightClickMenu ref="newbuilt" @newBuilt="GetNewBuilt" @openNew="GetOpenNew"></RightClickMenu>
            <div v-for="(col,colIndex) of person.collList" :key="colIndex">
              <el-tooltip class="box-item" effect="light" placement="right-end" :content="col.name">
                <div class="cont_item" @contextmenu.prevent="collClick(col,colIndex)" :class="[person.collActive===col.Id?'cont_item_active':'']" @dblclick="doubleClick(col,colIndex)" @click="collClick(col,colIndex)">
                  <RightClickCurrFile ref="currfile" v-if="person.currFile.Id===col.Id" @onMenus="GetonMenus" @openCurr="GetOpenCurr" :currItem="person.currFile"></RightClickCurrFile>
                  <div class="cont_item_ok" v-if="person.collActive===col.Id">
                    <svg t="1684373978603" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="9781" width="18" height="18"><path d="M510.567403 63.852903c-246.304387 0-446.663336 200.358949-446.663336 446.663336 0 246.304387 200.358949 446.663336 446.663336 446.663336 246.304387 0 446.765664-200.358949 446.765664-446.663336C957.230738 264.211852 756.87179 63.852903 510.567403 63.852903L510.567403 63.852903zM787.979614 386.084941 454.593784 719.573099c-7.981613 7.981613-20.977316 7.981613-28.958929 0l-43.694214-43.694214c0 0 0 0 0 0L237.145998 531.084241c-7.981613-7.981613-7.981613-20.977316 0-28.958929l43.694214-43.694214c7.981613-7.981613 20.977316-7.981613 28.958929 0L440.063156 588.592785 715.326471 313.329469c7.981613-7.981613 20.977316-7.981613 29.061257 0L787.979614 357.126012C796.063556 365.107625 796.063556 378.103328 787.979614 386.084941L787.979614 386.084941z" p-id="9782" fill="#2080F7"></path></svg>
                  </div>
                  <svg t="1684303283032" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3201" width="48" height="48"><path d="M0 128a51.2 51.2 0 0 1 51.2-51.2h350.24a51.2 51.2 0 0 1 47.0592 31.0336L473.6 166.4h499.2a51.2 51.2 0 0 1 51.2 51.2v537.6a51.2 51.2 0 0 1-51.2 51.2H51.2a51.2 51.2 0 0 1-51.2-51.2V128z" fill="#FFA000" p-id="3202"></path><path d="M89.6 249.6m51.2 0l742.4 0q51.2 0 51.2 51.2l0 460.8q0 51.2-51.2 51.2l-742.4 0q-51.2 0-51.2-51.2l0-460.8q0-51.2 51.2-51.2Z" fill="#FFFFFF" p-id="3203"></path><path d="M0 332.8m51.2 0l921.6 0q51.2 0 51.2 51.2l0 512q0 51.2-51.2 51.2l-921.6 0q-51.2 0-51.2-51.2l0-512q0-51.2 51.2-51.2Z" fill="#FFCA28" p-id="3204"></path></svg>
                  <el-input ref="itemInput" class="cont_item_input" @focus="onFocus($event)" @blur="InputBlur($event,col,0)" v-if="person.isClick&&colIndex===0" v-model="col.Name" />
                  <el-input ref="itemInput" class="cont_item_input" @focus="onFocus($event)" @blur="InputBlur($event,col,1)" v-else-if="col.isDoubleClick" v-model="col.Name" />
                  <div class="cont_item_name" v-else @dblclick.stop="doubleReName()">{{col.name}}</div>
                </div>
              </el-tooltip>
            </div>
          </div>
          <div class="cont_null" v-else>
            <div>当前文件夹还没有内容哦~, 快试试新建吧!</div>
            <el-button @click="newFileBtn">+新建文件夹</el-button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script lang='ts' setup>
import { onMounted, reactive, nextTick, ref, onUnmounted, watch } from 'vue';
import { ArrowRight } from '@element-plus/icons-vue'
import RightClickMenu from '@/components/view/collectCata/rightClickMenu.vue'
import RightClickCurrFile from '@/components/view/collectCata/rightClickCurrFile.vue'
import { ElMessage,ElMessageBox } from 'element-plus'
const person = reactive({
  collList1:[
    {level:1,Id:1,isDoubleClick:false,name:'物理'},
    {
      level:1,
      Id:2,
      isDoubleClick:false,
      name:'化学',
      list:[
        {
          level:2,
          Id:222,
          isDoubleClick:false,
          name:'化学1',
          list:[]
        },
        {
          level:2,
          Id:333,
          isDoubleClick:false,
          name:'化学2',
          list:[
            {
              level:3,
              Id:854,
              isDoubleClick:false,
              name:'化学22',
              list:[]
            }
          ]
        }
      ]
    },
    {level:1,Id:3,isDoubleClick:false,name:'生物'},
    {level:1,Id:4,isDoubleClick:false,name:'语文'},
    {level:1,Id:5,isDoubleClick:false,name:'数学幂函数算法精讲课程资料'},
    {level:1,Id:6,isDoubleClick:false,name:'数学三角函数计算公式'}
  ],
  collList:[],
  collActive: -1,
  isCloseColl:false,
  isClick:false, // 是否点击了新建文件夹
  currFile:{}, // 当前文件
  levelTitle:'',
  navList:[
    {name:'全部文件',Id:0}
  ],
  newName:'',
})
const itemInput=ref()
const newbuilt=ref()
const currfile=ref()

type TProps = {
  collData: any,
  isCollect:boolean
}
const props = withDefaults(defineProps<TProps>(), {})

watch(()=>props.isCollect,(newVal,oldVal)=>{
  if(newVal){
    person.isCloseColl=newVal
  }
})

onMounted(()=>{
  load()
  document.addEventListener("keyup", onxx)
})
onUnmounted(()=>{
  document.removeEventListener('keyup',onxx)
})
// 初始化
const load=()=>{
  person.collList = person.collList1
  // person.collList=props.collData
}
// 键盘事件
const onxx=(ev:any)=>{
  if(ev.keyCode===46){
    deleteCurr()
  }else if(ev.keyCode===113){
    Rename()
  }
}
// 删除当前
const deleteCurr=()=>{
  person.collList.forEach((co:any)=>{
    if(co.Id===person.currFile.Id){
      ElMessageBox({
        message:`确认删除名为 "${person.currFile.name}" 的文件夹吗?`,
        dangerouslyUseHTMLString: false,
        showCancelButton: true,
        confirmButtonText: '确认',
        cancelButtonText: '取消'
      }).then(() => {
        person.collList=person.collList.filter(f=> f.Id!==person.currFile.Id)
        person.collActive=-1
      })
    }
  })
}

// 返回上一级
const backPrev=()=>{
  let a=person.navList[person.navList.length-1]
  if(a.name!=='全部文件'){
    person.navList.pop()
  }
  // 根据当前Id调用列表接口
  // GetFavoritelist(a.ParentId)
}

// 切换气泡标题
const navChange=(item:any,index:number)=>{
  if(person.navList.length-1!==index){
    // 根据当前Id调用列表接口
  }
  // 给切换后赋值
  var items = person.navList.filter((nav:any,nIndex:number)=>nIndex<=index)
  person.navList=items
}

// 选中当前文件夹
const collClick=(item:any,index:number)=>{
  person.collActive=item.Id
  person.currFile=item
}

// 当前文件夹双击事件
const doubleClick=(item:any,index:number)=>{
  doubleBtn()
}
// 当前文件双击
const doubleBtn=()=>{
  person.collList=person.currFile.list
  person.collActive=-1
  person.navList.push({name:person.currFile.name})
}

// 重命名双击事件
const doubleReName=()=>{
  Rename()
}
// 重命名
const Rename=()=>{
  person.collList.forEach((co:any,coIndex:number)=>{
    if(co.name===person.currFile.name){
      co.isDoubleClick=true
      nextTick(()=>{
        itemInput.value[0].focus()
      })
    }
  })
}

// 新建目录
const newFileBtn=()=>{
  person.isClick=true
  if(person.isClick){
    newBtn()
  }
}
// 获取新建点击
const GetNewBuilt=(isClick:boolean)=>{
  person.isClick=isClick
  if(person.isClick){
    newBtn()
  }
}
// 新建文件夹
const newBtn=()=>{
  person.collList.unshift({
    level:1,
    Id:23,
    name:'新建文件夹',
    isDoubleClick:false
  })
  if(person.currFile){
    person.collActive=person.currFile.Id
  }else{
    person.collActive=person.collList[0].Id
  }
  // 获取焦点
  nextTick(()=>{
    itemInput.value[0].focus()
  })
}


// 获取是否打开新建
const GetOpenNew=(isopen:boolean)=>{
  if(isopen){
    person.collActive=-1
    // nextTick(()=>{
    //   const cuu=document.getElementsByClassName('curr_text')[0]
    //   cuu.classList.add('disnone')
    // })
    // newbuilt.value.closeMenu()
  }
}

// 获取当前文件夹的右击菜单
const GetonMenus=(item:any)=>{
  if(item.name==='重命名'){
    Rename()
  }else if(item.name==='打开'){
    doubleBtn()
  }else if(item.name==='删除'){
    deleteCurr()
  }
}

// 获取是否打开当前文件夹
const GetOpenCurr=(isopen:boolean)=>{
  if(isopen){
    person.collActive=-1
  }
}

// 输入框失去焦点
const InputBlur=async (e:any,col:any,isRevise:number)=>{
  col.isDoubleClick=false
  if(col.Name===''){
    ElMessage.error('命名不能为空!')
  }else{
    var models = person.collList.filter((x:any)=>x.Name===col.Name && x.Id!==col.Id)
    if(models.length>0){
      await addChildren(col.Name,col.Name,col.Id,0);
      col.Name=person.newName
    }
    person.isClick=false
    if(isRevise===0){
      // 调用新建接口

    }else if(isRevise===1){
      // 调用修改接口

    }
  }
}
// 新建文件夹重名的问题
const addChildren=async (oldname:string,name:string,Id:number,index:number)=>{
  var models = person.collList.filter((x:any)=>x.Name===name && x.Id!==Id)
  if(models.length>0){
    index=index+1;
    name=oldname+index;
    await addChildren(oldname,name,models[0].Id,index)
  }else{
    person.newName=oldname+index
  }
}

// 输入框获取焦点
const onFocus=(e:any)=>{
  e.currentTarget.select();
}

</script>
<style lang='scss' scoped>
.disnone{
  display: none !important;
}
.coll_pop{
  position: fixed;
  left: 0;
  top: 0;
  z-index: 99;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,.3);
  .coll_box{
    margin: 5% auto 0 auto;
    width: 60%;
    background: #fff;
    height: 750px;
    .coll_top{
      padding: 10px 20px;
      justify-content: space-between;
      border-bottom: 1px solid #ddd;
      .el-icon{
        font-size: 20px;
      }
    }
    .coll_center{
      padding: 10px 20px;
      .coll_title{
        margin-top: 10px;
        .coll_title_back{
          margin-right: 10px;
          cursor: pointer;
          color: #2080F7;
        }
        .coll_title_nav{
          cursor: pointer;
        }
      }
    }
    .coll_cont{
      padding: 10px 20px;
      .cont_scroll{
        position: relative;
        height: calc(100vh - 340px);
        overflow: hidden;
        .cont_null{
          width: 100%;
          height: calc(100vh - 340px);
          display: flex;
          flex-direction: column;
          justify-content: center;
          text-align: center;
          color: #999;
          .el-button{
            background: #2080F7;
            color: #fff;
            border: 0;
            padding: 20px 20px;
            width: 150px;
            margin: 15px auto 0 auto;
          }
        }
      }
      .cont_list{
        display: flex;
        flex-wrap: wrap;
        :deep(.cont_item){
          margin-right: 15px;
          margin-bottom: 15px;
          max-width: 120px;
          min-width: 120px;
          min-height: 120px;
          padding: 10px;
          cursor: pointer;
          user-select:none;
          text-align: center;
          overflow: hidden;
          position: relative;
          z-index: 0;
          border: 1px solid #fff;
          .cont_item_ok{
            position: absolute;
            left: 5px;
            top: 5px;
          }
          .cont_item_name{
            display: -webkit-box;
            -webkit-box-orient: vertical;
            -webkit-line-clamp: 2;
            overflow: hidden;
            height: 40px;
            position: absolute;
            width: 80%;
          }
          .el-input__wrapper{
            border-radius: 0;
            height: 30px;
            border: 1px solid #2080F7;
            background: #fff;
          }
        }
        .cont_item_active{
          border: 1px solid #2080F7;
          background: #E7F3FF;
        }
        .cont_item:hover{
          background: #f6faff;
          border: 1px solid #f6faff;
        }
      }
    }
  }
}
</style>

三、鼠标右键 红框区域的 新建文件夹功能组件

1、示例图

2、rightClickMenu.vue 文件,实现代码如下

<template>
<!-- 外层最大盒子设置contextmenu事件-->
<div class="defenseScreen" @contextmenu.prevent="openMenu($event)">
  <div class="mainBox"></div>
  <!-- 右击弹框设置 -->
  <div class="textmenu" v-show="person.visible" :style="{ left: person.left + 'px', top: person.top + 'px' }">
    <div class="textmenu_list" @click="newBuilt">新建文件夹</div>
  </div>
</div>
</template>
<script lang='ts' setup>
import { reactive, watch } from 'vue';
const person = reactive({
  visible: false, // 控制菜单栏的隐藏与展示
  top: 0, // 定义菜单栏出现的位置
  left: 0,
  rightMenu:[
    {name:'新建文件夹'}
  ]
})

const emits = defineEmits(['newBuilt','openNew']);
watch(()=>person.visible,(newVal)=>{
   // 如果打开了菜单栏
   if (newVal) {
    // 在body上设置点击事件 , 移除菜单栏
    document.body.addEventListener('click', closeMenu)
  } else {
    // 如果没有出现菜单栏 , 就直接移除body上的点击事件,以免其他页面出现问题
    document.body.removeEventListener('click', closeMenu)
  }
})

// 鼠标右击
const openMenu=(e:any)=> {
  // 设置菜单栏出现的位置
  let x = e.clientX;
  let y = e.clientY;
  person.top = y;
  person.left = x;
  // 右击打开了菜单栏
  person.visible = true;
  emits('openNew', person.visible);
}

// 菜单栏关闭执行回调
const closeMenu=() => {
  person.visible = false;
}
// 新建文件夹
const newBuilt=()=>{
  emits('newBuilt', true);
}

</script>
<style lang='scss' scoped>
.defenseScreen {
  position: absolute;
  width: 100%;
  height: 100%;
  background-size: 100% 100%;
  .textmenu {
    margin: 0;
    background:#fff;
    z-index: 3000;
    position: fixed;
    list-style-type: none;
    padding: 5px;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 400;
    color: #333;
    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
  }
  .textmenu_list{
    margin: 0;
    padding: 7px 16px;
    cursor: pointer;
    transition:all .3s;
  }
  .textmenu_list:hover {
    background: #2080F7;
    color: white;
  }
}
</style>

四、当前文件夹 鼠标右键 红框区域的功能组件

1、示例图

2、rightClickCurrFile.vue 文件,实现代码如下

<template>
<!-- 外层最大盒子设置contextmenu事件-->
<div class="curr_Menu" @contextmenu.prevent="openMenu($event)">
  <div class="mainBox"></div>
  <!-- 右击弹框设置 -->
  <div class="curr_text" v-show="person.visible" :style="{ left: person.left + 'px', top: person.top + 'px' }">
    <div class="curr_item" v-for="(menu,meIndex) in person.rightMenu" :key="meIndex" @click="onMenu(menu,meIndex)">{{menu.name}}</div>
  </div>
</div>
</template>
<script lang='ts' setup>
import { reactive, watch } from 'vue';
const person = reactive({
  visible: false, // 控制菜单栏的隐藏与展示
  top: 0, // 定义菜单栏出现的位置
  left: 0,
  rightMenu:[
    {name:'打开'},
    {name:'重命名'},
    {name:'删除'}
  ]
})

const emits = defineEmits(['onMenus','openCurr']);

watch(()=>person.visible,(newVal)=>{
   // 如果打开了菜单栏
   if (newVal) {
    // 在body上设置点击事件 , 移除菜单栏
    document.body.addEventListener('click', closeMenu)
  } else {
    // 如果没有出现菜单栏 , 就直接移除body上的点击事件,以免其他页面出现问题
    document.body.removeEventListener('click', closeMenu)
  }
})

// 鼠标右击
const openMenu=(e:any)=> {
  // 设置菜单栏出现的位置
  let x = e.clientX;
  let y = e.clientY;
  person.top = y;
  person.left = x;
  // 右击打开了菜单栏
  person.visible = true;
  emits('openCurr', person.visible);
}

// 菜单栏关闭执行回调
const closeMenu=() => {
  person.visible = false;
}
// 右击菜单
const onMenu=(item:any,index:number)=>{
  emits('onMenus', item);
}

</script>
<style lang='scss' scoped>
.curr_Menu {
  position: absolute;
  width: 100%;
  height: 100%;
  background-size: 100% 100%;
  .curr_text {
    margin: 0;
    background:#fff;
    position: fixed;
    z-index: 99999;
    padding: 5px;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 400;
    color: #333;
    box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
  }
  .curr_item{
    margin: 0;
    padding: 7px 16px;
    cursor: pointer;
    transition:all .3s;
    text-align: left;
  }
  .curr_item:hover {
    background: #2080F7;
    color: white;
  }
}
</style>

       希望我的愚见能够帮助你哦~,若有不足之处,还望指出,你们有更好的解决方法,欢迎大家在评论区下方留言支持,大家一起相互学习参考呀~

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值