<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>
该段代码实现悬浮框拖拽,表单提交,表单验证,表单隐藏与展开。悬浮框拖拽阻止悬浮框出浏览器外。