最近遇到一个需求:前端定制化生成合同模板,生成时可以在指定位置拖放指定的控件,可动态编辑指定控件的属性和位置,最后将控件的位置等属性传给后台,后续使用模板签署合同时,乙方可在模板上指定位置签署。
点击左边控件列表=》添加控件到文件上=》点击文件上的控件激活该控件=》编辑该控件的相关字段=》提交数据
控件类型有多种,支持多个控件同时添加,文本类型支持长宽拉伸设置
刚好有一个很好用的vue插件,那就是今天的猪脚了:vue-draggable-resizable
github地址:https://github.com/mauricius/vue-draggable-resizable,文档挺详细的,使用:
npm i vue-draggable-resizable -S
main.js
import 'vue-draggable-resizable/dist/VueDraggableResizable.css';
可以直接在页面引入:
import VueDraggableResizable from 'vue-draggable-resizable';
export default {
components: { VueDraggableResizable },
、、、
}
渲染效果:
已经很棒了,但依然需要做进一步的定制化,满足我们项目的需求:
1、需求:由于控件可以添加多个、多种类,多以肯定会使用v-for生成多个拖拽控件,这就涉及到,当我点击控件对其属性进行编辑的时候需要知道当前点击的控件具体是哪一个。
实际情况:点击事件是没有返回参数
解决办法:把源代码拷贝出来并修改
找到node_modules>vue-draggable-resizable>src>components文件夹下的vue-draggable-resizable.vue文件和src下的utils下的js依赖拷贝出来,我是放到自己项目的src>components下
这样页面的引入方式修改一下:
import VueDraggableResizable from 'vue-draggable-resizable';
这样就把源代码copy出来作为项目的一个组件,方便我们定制化修改
给组件添加customId作为props属性,渲染组件时把唯一id传进去,在actived事件时传递出来,父级页面就知道当前编辑的控件id了,源代码 index.vue
所有activated事件都把id传递出去
源代码 index.vue
demo.vue
// 点击控件
onActivated(customId) {
// console.log(customId);
this.activedId = customId;
},
2、需求:激活某一控件后,需在右侧编辑该控件的属性,此时该控件必须高亮
实际:默认情况下,只要不是点击控件自身,不管点击页面哪里,该控件都会失去焦点,不再高亮,从源码可以看到做了判断:
官网有一个配置属性,preventDeactivation=true时点击其它地方,已激活的控件不会失去焦点,对于单个控件来说没问题,但当页面有多个控件时,即便是点击其它控件也不会失去焦点,这样就没法切换控件,无法满足要求。
解决办法:修改源码判断标准
新增props属性:commonClassName用于区分点击的元素是否为控件或控件内slot元素
源代码 index.vue
绑定到元素上
源代码 index.vue
修改判定逻辑
源代码 index.vue
使用组件时,组件及slot元素统一都绑上一个commonClassName
demo.vue
如果点击的元素不是控件(即没有统一class属性的元素),才让上一个激活的控件失去焦点,就达到了切换的功能。
3、需求:点击左侧控件列表添加控件时,默认当前添加控件为激活状态,即高亮,不用再一次点击才激活
解决办法:修改源码,添加控件的同时,手动触发组件的activated事件
源代码 index.vue
可以看到activated事件是在elementDown事件中触发的,在控件添加完后使用this.$refs.refName.method()方法调用组件内部方法即可。
添加控件的同时绑定一个唯一的id,我这里用的是时间戳
demo.vue
添加控件:
demo.vue
// 添加控件
addControl(type) {
const controlObjMap = {
1: {
customId: Date.now(),
width: 200,
height: 30,
x: 300,
y: 40,
className: 'lq-draggable-text',
type: 'text', // 控件类型
handles: ['mr'],
name: '文本框',
fontsSize: 10,
signatory: 0 // 签署方默认甲方
},
2: {
customId: Date.now(),
width: 80,
height: 80,
x: 0,
y: 140,
type: 'seal', // 控件类型
name: '印章',
signatory: 0 // 签署方默认甲方
},
3: {
customId: Date.now(),
width: 80,
height: 40,
x: 0,
y: 260,
type: 'sign', // 控件类型
name: '签名',
signatory: 0 // 签署方默认甲方
},
4: {
customId: Date.now(),
width: 80,
height: 30,
x: 0,
y: 380,
type: 'date', // 控件类型
name: '日期',
fontsSize: 10,
signatory: 0 // 签署方默认甲方
},
5: {
customId: Date.now(),
width: 80,
height: 30,
x: 200,
y: 380,
type: 'select', // 控件类型
name: '选项',
fontsSize: 10,
signatory: 0 // 签署方默认甲方
}
};
this.controlsArr.push(controlObjMap[type]);
this.setControlActive(controlObjMap[type].customId);
},
// 手动设置刚才添加的控件为选中状态
setControlActive(customId) {
this.$nextTick(() => {
// console.log(this.$refs[customId][0]);
this.$refs[customId][0].elementDown();
});
}
由于外部调用elementDown方法,没有event对象最为参数,所以需要对该函数做个处理
源代码 index.vue
elementDown(e) {
// 没有e则是外部调用方式
const target = e ? e.target || e.srcElement : '';
const classNameArr = target.className
? target.className.split(' ')
: [];
// 被点击元素不是关闭按钮
if (classNameArr.includes('close')) {
return;
}
if (e && this.$el.contains(target)) {
、、、、、
} else {
// 用于父组件直接调用
if (!this.enabled) {
// console.log(666);
this.enabled = true;
this.$emit('activated', this.customId);
this.$emit('update:active', true);
if (this.draggable) {
this.dragging = true;
}
}
}
},
4、需求:每个控件上需要一个关闭按钮,点击时需单独处理,和激活事件区分开来
给关闭按钮绑定close属性
demo.vue
源代码 index.vue
ok!这样修修改改总算能满足要求了,在此基础上继续扩展也很方便了。
项目地址:https://github.com/LandQ123/lq-components.git