自定义vue指令v-drag & 封装自定义可拖拽弹框 & id定义样式、computed实现动态style动态class & 具名插槽name属性定义slot & 引入全局组件 & 定义全局样式
效果
代码
1、主页面
index.vue
<template>
<div>
<el-button type="primary" size="default" @click="abc">点击</el-button>
<ModalFrame v-model="isShowModel" v-drag="greet"></ModalFrame>
</div>
</template>
<script>
export default {
data() {
return {
isShowModel:false,
}
},
methods: {
abc(){
this.isShowModel = true
},
}
}
</script>
2、封装组件
2.1、自定义弹框组件
src\components\modalFrame\modalFrame.vue
<template>
<div id="shade-model" :class="isMark?'bg':'noBg'" v-show="modelValue">
<div id="model-frame" :style="frameStyle">
<div class="model-frame-title" v-show="title">
<slot name="title">
<p>提示</p>
</slot>
<span class="close-model-button" @click.stop="closeModelFrame" v-show="shut">×</span>
</div>
<div class="model-frame-content">
<slot name="content"></slot>
</div>
<div class="model-frame-footer">
<button class="cancel" @click.stop="closeModelFrame" v-show="cancel">取消</button> 
<slot name="footer"></slot>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
modelValue: {
type: Boolean
},
width: {
type: Number,
default: 460
},
height: {
type: Number,
default: 270
},
cancel: {
type: Boolean,
default: true
},
title: {
type: Boolean,
default: true
},
shut: {
type: Boolean,
default: true
},
isMark:{
type: Boolean,
default: true
}
},
methods: {
//关闭模态框
closeModelFrame() {
this.$emit('update:modelValue',false)
this.$emit('cancelReply') //通知父组件调用函数
}
},
computed: {
frameStyle: function() {
return {
width: this.width + "px",
height: this.height + "px",
marginLeft: -(this.width / 2) + "px",
marginTop: -(this.height / 2) + "px"
}
}
}
}
</script>
<style>
#shade-model{
width:100%;
height:100%;
z-index: 11;
top: 0;
left: 0;
position:fixed;
}
.bg{
background-color:rgba(0,0,0,0.3);
}
.noBg{
background-color:transparent;
}
#model-frame{
background:white;
position:absolute;
left:50%;
top:50%;
border: 1px solid #cccccc;
border-radius: 4px;
}
#model-frame .close-model-button{
position: absolute;
right: 10px;
top: 12px;
cursor: pointer;
border-radius: 50%;
width: 32px;
height: 32px;
text-align: center;
line-height: 32px;
color: #999;
}
#model-frame .model-frame-title {
width: 100%;
height: 56px;
border-bottom: 1px solid #d9d9d9;
font-weight: 700;
font-style: normal;
font-size: 16px;
position: relative;
}
#model-frame .model-frame-title p{
padding-left: 24px;
color: #000;
height:56px;
line-height: 56px;
text-align: left;
}
#model-frame .model-frame-content {
width: 100%;
overflow: hidden;
font-size: 14px;
color: rgb(51, 51, 51);
box-sizing: border-box;
padding: 16px 20px 16px;
}
#model-frame .model-frame-footer {
text-align: right;
border-top: 1px solid #d9d9d9;
position: absolute;
bottom: 0;
height: 56px;
line-height: 56px;
width: 100%;
padding-right: 20px;
box-sizing: border-box;
}
#model-frame .model-frame-footer>div{
display: inline-block;
}
#model-frame .model-frame-footer .cancel {
background-color: #fff;
font-size: 14px;
color: rgb(74, 74, 74);
text-decoration: underline;
cursor: pointer;
}
#model-frame .model-frame-footer .ok{
width: 80px;
height: 32px;
line-height: 32px;
text-align: center;
color: #fff;
font-weight: 400;
font-style: normal;
font-size: 14px;
background-color: #4385f4;
border-radius: 23px;
margin-left: 20px;
cursor: pointer;
margin-right: 20px;
}
#model-frame .model-frame-footer .ok:hover{
transition: all .4s;
background-color: #6CA0F8;
}
</style>
2.2、自定义vue指令
src\directive\drag.vue
<script>
export default{
mounted(el, binding) {
let oDiv = el; //当前元素
let self = this; //上下文
oDiv.onmousedown = function (e) {
if(e.target.value == undefined){
//鼠标按下,计算当前元素距离可视区的距离
let disX = e.clientX - oDiv.offsetLeft;
let disY = e.clientY - oDiv.offsetTop;
document.onmousemove = function (e) {
//通过事件委托,计算移动的距离
let l = e.clientX - disX;
let t = e.clientY - disY;
//移动当前元素
oDiv.style.left = l + 'px';
oDiv.style.top = t + 'px';
//将此时的位置传出去
binding.value({x:e.pageX,y:e.pageY})
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
}
};
}
}
</script>
3、注册全局组件和指令
src\main.ts
import { createApp, ref } from 'vue'
import App from './App.vue'
const app = createApp(App)
/*公共模态框组件*/
app.component('ModalFrame', ModalFrame);
import ModalFrame from './components/modalFrame/modalFrame.vue';
/*拖拽*/
app.directive('drag', Drag)
import Drag from './directive/drag.vue'
实例
效果
代码
index.vue
<template>
<div>
<el-button type="primary" size="default" @click="abc">点击</el-button>
<ModalFrame v-model="isShowModel" v-drag="greet" :width="380" :height="315">
<template v-slot:title>
<p>确认报名</p>
</template>
<template v-slot:content>
<div class="gamePage_content">
<p><span>姓名</span><span>: {{ userInfo.userName}}</span></p>
<p><span>公司名称</span><span>: {{ userInfo.baseOrgName }}</span></p>
<!-- <p><span>所属部门</span><span>: {{ userInfo.orgNameChineseTotal }}</span></p>
<p><span>邮箱</span><span>: {{ idCard(userInfo.email)}}</span></p>
<p><span>联系方式</span><span>: {{ idCard(userInfo.mobilePhone)}}</span></p> -->
<!-- --------------------------------------------------------------------------------------- -->
<!-- <p><span>姓名</span><span>: {{ userInfo.userName | sm }}</span></p>
<p><span>公司名称</span><span>: {{ userInfo.orgName }}</span></p>
<p><span>所属部门</span><span>: {{ userInfo.orgNameChineseTotal }}</span></p>
<p><span>邮箱</span><span>: {{ userInfo.email | sm | idCard }}</span></p>
<p><span>联系方式</span><span>: {{ userInfo.mobilePhone | sm | idCard }}</span></p> -->
<div class="gamePage_footer">
<input type="checkbox" v-model="gamePopingAgreement" /> 同意<span class="gameKnow"
@click="application">《报名须知》</span><span v-show="allMessage"
style="font-size:12px;color:red">请勾选同意按钮</span>
</div>
</div>
</div>
</template>
<script>
import { activityEnrollApi } from '@/api/activity'
export default {
data() {
return {
isShowModel:false,
gamePopingAgreement: false,
}
},
methods: {
abc(){
this.isShowModel = true
},
//报名须知
application() {
this.gameInformation = true;
},
//确定报名
async gamePopingAgree() {
try {
let result = await activityEnrollApi({activityId: this.$route.query.id})
if(result.data.code == 200){
this.message.success('活动报名成功,可前往个人中心查看我参与的活动')
this.showPersonalApply = false
// this.gonuew = true;
// if (this.questionType == "0" || this.questionType == "1") {
// // 0 1 自抽 null 不抽提
// window.sessionStorage.setItem('actId', this.$route.query.id);
// this.$router.push("/activity/Randomquestions")
// }
}
} catch (error) {
console.log(error)
}
},
}
}
</script>
<style lang="scss" >
/*马上报名弹框*/
.modalFrame {
.gamePage_content {
p {
line-height: 28px;
span:first-child {
width: 60px;
display: inline-block;
text-align: justify;
text-align-last: justify;
margin-right: 4px;
}
}
}
.gamePage_footer {
margin-top: 10px;
}
</style>
src\components\constant.js
export default {
ieopActivity: "/ieop-mtg-activity", // 申请表网关标识
}
src\api\activity\index.js
import axios from '@/utils/request'
import { getApiUrl } from "@/utils/tool";
import constant from "@/components/constant";
const baseUrl = getApiUrl();
// 活动报名
export const activityEnrollApi = (params)=>{
return axios.post(baseUrl + constant.ieopActivity + '/activity/compete/enroll',params)
}
src\utils\tool.js
export function getApiUrl(v) {
return process.env.VUE_APP_API_HOST
}
.env.dev
VUE_APP_API_HOST = '/gp'
vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
devServer: {
proxy: {
'/gp': {
target: 'http://27.196.111.15:18080',
changeOrigin: true,
pathRewrite: {
'^/gp': '/'
}
},
// '/img': {
// target: 'http://192.168.67.86:9595',
// changeOrigin: true,
// pathRewrite: {
// '^/img': '/'
// }
// },
},
port: 9000,
// host:'27.196.104.110',
host:'0.0.0.0',
open:true
},
})
定义公共样式
public\css\CommonStyle.css
/*列表标题hover效果*/
.listHover:hover{
color:#4385f4;
}
/* element plus 分页器 位置 */
.pagination_center{
justify-content: center;
}
.pagination_right{
justify-content: right;
}
/*
阴影
*/
.Public-Shaow{
color: #DCDFE6;
box-shadow: 0 0 18px 0 rgba(0, 0, 0, 0.15);
}
.mb20{
margin-bottom: 20px;
}
.ml20{
margin-left: 20px;
}
.mt20{
margin-top: 20px;
}
.mr20{
margin-right: 20px;
}
/* element plus 分页器 位置 */
.pagination_center{
justify-content: center;
}
.pagination_right{
justify-content: right;
}
/* div p span 英文换行 */
div,p,span{
word-break: break-all;
}
.el-popper:focus{
outline: none !important;
}
.el-input__wrapper:focus{
outline: none !important;
}
.swiper-button-prev:focus{
outline: none !important;
}
.swiper-button-next:focus{
outline: none !important;
}
.el-input-number .el-input__inner{
text-align: left !important;
}
src\App.vue
<template>
<div id="app" >
<my-header></my-header>
</div>
</template>
<script setup>
import "../public/css/CommonStyle.css"
</script>
<style lang="scss" scoped>
#app {
background: #fff;
width: 100%;
height: 100%;
}
</style>