naive-ui+vue3+可拖拽悬浮框

这段代码展示了如何在Vue中使用NaiveUI组件构建一个可拖动的悬浮框,包含表单提交、验证以及表单的显示/隐藏功能,确保悬浮框不会超出浏览器边界。
摘要由CSDN通过智能技术生成
<template>
    <div style="position: fixed;z-index: 9999;" :style="`top:${positions.y}%;left:${positions.x}%;`" v-if="showIcon">
  <div class="message-out">
        <div class="message-header"  @mouseleave="mousemove" @mouseup="mouseup" @mousemove="mousemove" @mousedown="mousedown">
            <span style="">这是悬浮框</span>
            <span style="cursor: pointer;padding-right: 10px;margin-top: 5px;" @click="closeMain" v-if="mainShow" class="message-item"><n-icon class="message-item"><RemoveOutline style="font-size: 24px;" class="message-item"/></n-icon></span>
            <span style="cursor: pointer;padding-right: 10px;" @click="showMain" v-if="!mainShow" class="message-item"><n-icon class="message-item"><TabletLandscapeOutline style="font-size: 18px;" class="message-item"/></n-icon></span>
        </div>
        <div class="message-main" v-if="mainShow">
            <span>这是悬浮框提交的表</span>
            <n-form
    ref="formRef"
    :label-width="80"
    :model="formValue"
    :rules="rules"
    size="medium"
    style="padding-top: 16px;"
  >
  <n-form-item label="" path="content">
    <n-input v-model:value="formValue.content" placeholder="请在此输入留言内容,我们会尽快与您联系。(必填)" type="textarea" :autosize="{minRows: 3,maxRows: 5}" style="font-size: 12px;"></n-input>
  </n-form-item>
    <n-form-item label="" path="customer">
      <n-input v-model:value="formValue.customer" placeholder="输入姓名" >
        <template #prefix v-if="showIcon">
        <n-icon>
          <PersonOutline />
        </n-icon>
      </template>
    </n-input>
    </n-form-item>
    <n-form-item label="" path="phone">
      <n-input v-model:value="formValue.phone" placeholder="电话号码(必填)" >
        <template #prefix v-if="showIcon">
        <n-icon>
          <CallOutline />
        </n-icon>
      </template>
    </n-input>
    </n-form-item>
    <n-form-item label="" path="email">
      <n-input v-model:value="formValue.email" placeholder="输入邮箱" >
        <template #prefix v-if="showIcon">
        <n-icon>
          <MailOutline />
        </n-icon>
      </template>
    </n-input>
    </n-form-item>
    <n-form-item label="" path="province">
      <n-input v-model:value="formValue.province" placeholder="输入地址" >
        <template #prefix v-if="showIcon">
        <n-icon>
          <HomeOutline />
        </n-icon>
      </template>
    </n-input>
    </n-form-item>
    <n-form-item style="display: flex;flex-wrap: wrap;justify-content: flex-end;">
      <n-button attr-type="button" @click="handleValidateClick" type="info" :loading="loading">
        提交
      </n-button>
    </n-form-item>
  </n-form>
        </div>
  </div>
</div>
</template>

<script setup lang="ts">
import { ref, reactive } from 'vue'
import { type FormInst, NButton, NForm,NFormItem,NInput,NIcon,createDiscreteApi  } from "naive-ui"
import { RemoveOutline, PersonOutline,TabletLandscapeOutline,CallOutline,MailOutline,HomeOutline } from "@vicons/ionicons5";
const {message} = createDiscreteApi(["message"])
// **********************************************************************移动窗口start*****************************************************
const positions = reactive({
    x: 1, //left:x
    y: 50, //top:y
    leftOffset: 0, //鼠标距离移动窗口左侧偏移量
    topOffset: 0, //鼠标距离移动窗口顶部偏移量
    isMove: false, //是否移动标识
})
    const mousedown =(event:any)=>{
      if(event.target.className.baseVal === 'message-item' || event.target.className === 'message-item'|| event.target.localName === 'path'|| event.target.localName === 'rect') {
        if (event.target.localName === 'span') {
          positions.leftOffset = event.offsetX+(200);
          positions.topOffset = event.offsetY;
        } else {
          positions.leftOffset = event.offsetX+(240-37);
          positions.topOffset = event.offsetY+(4);
        }
      } else{
        //鼠标按下事件
        positions.leftOffset = event.offsetX;
        positions.topOffset = event.offsetY;
      }
      positions.isMove = true;
    }
    // Y轴判断
    const checkY = (event:any,height:number,h:any) => {
      if (event.clientY - positions.topOffset <= 0) {
          positions.y = 0;
        } else if ((event.clientY - positions.topOffset+height)>=h) {
          positions.y = (h-height)/h*100;
        } else {
          let topy = (event.clientY - positions.topOffset) / h;
          positions.y = topy * 100;
        }
    }
    // X轴判断
    const checkX = (event:any,width:number,w:any,height:number,h:any) => {
      if (event.clientX - positions.leftOffset <= 0) {
        positions.x = 0;
        checkY(event,height,h);
      } else if ((event.clientX - positions.leftOffset+width)>=w) {
        positions.x = (w-width)/w*100;
        checkY(event,height,h);
      } else {
        let leftx = (event.clientX - positions.leftOffset) / w;
        positions.x = leftx * 100;
        checkY(event,height,h);
      }
    }
    //鼠标移动
    const mousemove=(event:any)=> {
      if (!positions.isMove) {
        return;
      }
      let w =
        window.innerWidth ||
        document.documentElement.clientWidth ||
        document.body.clientWidth;

      let h =
        window.innerHeight ||
        document.documentElement.clientHeight ||
        document.body.clientHeight;  
      if(mainShow.value) {
      checkX(event,242,w,462,h)
      } else {
      checkX(event,242,w,46,h)
      }
    }
    //鼠标抬起
    const mouseup=()=>{
      positions.isMove = false;
    }
    // 展开移动框
    const showMain = (event:any) => {
      mainShow.value = true;
      let h =
        window.innerHeight ||
        document.documentElement.clientHeight ||
        document.body.clientHeight;  
      if (h-event.clientY <= 462) {
        positions.y = (h-462)/h*100;
      }
    }
    // 关闭移动框
    const closeMain = () => {
      mainShow.value = false;
    }
// **********************************************************************移动窗口end*****************************************************
const mainShow = ref(true)
const loading = ref(false)
const formRef = ref<FormInst | null>(null)
// const message = useMessage()
const formValue = reactive({
      content: '',
      customer: '',
      phone: '',
      email: '',
      province: '',
      })
    const rules=reactive({
      content: {
            required: true,
            message: '请输入留言内容',
            trigger: 'input'
          },
        phone: {
          required: true,
          message: '请输入电话号码',
          trigger: ['input']
        }})
  const handleValidateClick = (e: MouseEvent)=>{
  e.preventDefault()
  formRef.value?.validate(async (errors) => {
    if (!errors) {
      loading.value = true
       //访问接口
      // console.log(963258,data.value)
        // console.log('success');
      
    } else {
      console.log(errors)
      // message.error('Invalid')
    }
  })
}
      const showIcon = ref(false)
      onMounted(()=>{
        showIcon.value = true
        document.addEventListener('mousemove', mousemove)
        document.addEventListener('mouseup', mouseup)
        document.addEventListener('mouseleave', mouseup)
      })
      // ********************************************************************接口联调*****************************************************
</script>

<style scoped>
.message-out {
    width: 240px;
    /* height: 440px; */
    border-radius: 8px;
    z-index: 3;
    box-shadow: 0 8px 40px #0006;
}
.message-header {
    height: 46px;
    line-height: 46px;
    font-size: 14px;
    padding: 0 3px 0 8px;
    background-color: var(--3197eff8);
    border-top-left-radius: 5px;
    border-top-right-radius: 5px;
    display: flex;
    justify-content: space-between;
    cursor: move;
    background-color: #6696FF;
    color: #fff;
}
.message-main{
    padding: 8px 12px;
    background-color: #fff;
    border-bottom-left-radius: 8px;
    border-bottom-right-radius: 8px;
}
.n-form-item{
    grid-template-rows: none;
}
::v-deep .n-form-item .n-form-item-feedback-wrapper{
    font-size: 12px;
    margin-bottom: -8px;
}
::v-deep .n-form-item .n-form-item-feedback-wrapper:not(:empty) {
    padding: 0px !important;
}
.__button-lmmi {
    --n-bezier: cubic-bezier(.4, 0, .2, 1);
    --n-bezier-ease-out: cubic-bezier(0, 0, .2, 1);
    --n-ripple-duration: .6s;
    --n-opacity-disabled: 0.5;
    --n-wave-opacity: 0.6;
    font-weight: 400;
    --n-color: #6696ff;
    --n-color-hover: #6696ff;
    --n-color-pressed: #6696ff;
    --n-color-focus: #6696ff;
    --n-color-disabled: #6696ff;
    --n-ripple-color: #6696ff;
    --n-text-color: #FFF;
    --n-text-color-hover: #FFF;
    --n-text-color-pressed: #FFF;
    --n-text-color-focus: #FFF;
    --n-text-color-disabled: #FFF;
    --n-border: 1px solid #6696ff;
    --n-border-hover: 1px solid #6696ff;
    --n-border-pressed: 1px solid #6696ff;
    --n-border-focus: 1px solid #6696ff;
    --n-border-disabled: 1px solid #6696ff;
    --n-width: initial;
    --n-height: 34px;
    --n-font-size: 14px;
    --n-padding: 0 14px;
    --n-icon-size: 18px;
    --n-icon-margin: 6px;
    --n-border-radius: 3px;
    font-size: 12px;
    border-radius: 4px;
    width: 64px;
}
.message-header .__icon-d{
    font-size: 24px;
    line-height: 46px;
}
.__input-m {
    --n-caret-color: #000000;
    --n-border-hover: 1px solid #bebebe;
    --n-border-focus: 1px solid #bebebe;
    --n-box-shadow-focus: 0 0 0 2px #f5f5f5;
    --n-loading-color: #f5f5f5;
}
.message-main ::v-deep .n-input .n-input__input, .n-input .n-input__textarea{
    font-size: 12px;
}
.__input-nmlbzk-m {
    --n-border-hover: 1px solid #a6a7a8;
    --n-border-focus: 1px solid #ababac;
    --n-box-shadow-focus: 0 0 0 2px rgba(182, 182, 182, 0.2);
}
</style>

该段代码实现悬浮框拖拽,表单提交,表单验证,表单隐藏与展开。悬浮框拖拽阻止悬浮框出浏览器外。

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue树形复选是一种常见的UI组件,可以用于展示树形结构的数据,并支持多选功能。Naive-UI是一个基于VueUI组件库,提供了丰富的组件和样式,可以方便地实现树形复选功能。 下面是一个使用Naive-UI组件库实现Vue树形复选的示例: ```vue <template> <div> <n-tree :data="treeData" :default-expanded-keys="defaultExpandedKeys" :default-checked-keys="defaultCheckedKeys" show-checkbox @check-change="handleCheckChange" ></n-tree> </div> </template> <script> import { NTree } from 'naive-ui' export default { components: { NTree }, data() { return { treeData: [ { label: 'Node 1', children: [ { label: 'Node 1-1', children: [ { label: 'Node 1-1-1' }, { label: 'Node 1-1-2' } ] }, { label: 'Node 1-2' } ] }, { label: 'Node 2', children: [ { label: 'Node 2-1' }, { label: 'Node 2-2' } ] } ], defaultExpandedKeys: ['Node 1'], defaultCheckedKeys: ['Node 1-1-1'] } }, methods: { handleCheckChange(checkedKeys) { console.log('Checked keys:', checkedKeys) } } } </script> ``` 在上面的示例中,我们使用了Naive-UI的NTree组件来展示树形数据。通过设置`data`属性传入树形数据,`default-expanded-keys`属性设置默认展开的节点,`default-checked-keys`属性设置默认选中的节点。同时,我们还设置了`show-checkbox`属性来显示复选,并通过`check-change`事件监听复选的变化。 你可以根据自己的需求修改`treeData`、`defaultExpandedKeys`和`defaultCheckedKeys`来适配你的数据和默认选项。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值