前言
最近接了一个需求,要求:由于页面太多,实施自己在图片上进行画框然后实现操作引导.这个需求分为两步:
1.用户需要在页面上先上传指定大小图片
2.用户在图片内绘制区域,并输入操作引导的引导语
3.绘制好后保存到数据库中
4.在项目中按需进行查询指定的引导即可
引入相关依赖配置
1.intor.js
大家可以看我之前的文章是如何下载依赖引入的传送门→前端使用intro.js实现页面操作引导
话不多说开始操作↓
详细步骤
这里我是用html文件写的,大家换成vue直接按需粘过去就行
需要更改的地方:
1.上传图片的地址连接,换成自己公司的地址
2.样式可以自行更改~
详细步骤备注我就写在代码注释里面啦
<!DOCTYPE html>
<html lang="zh_CN">
<head th:include="include :: header">
<title>引导设置</title>
<!-- 引入操作引导样式组件 -->
<link rel="stylesheet" th:href="@{/css/introjs.css}">
</head>
<style>
#big_Img {
position: relative;
/*使大图随着小图移动的定位*/
z-index: 2;
}
#big_Img .requ {
border: 2px rgba(255, 27, 27, 0.76) dashed;
position: absolute;
cursor: move;
resize: both;
/*使绘制的div可以拖动改变大小的样式 */
overflow: auto;
z-index: 999;
}
.drag {
border: 1px #5da8ff solid;
width: 50px;
height: 24px;
background: #fff;
cursor: pointer;
margin: auto;
margin-top: 8px;
}
.drag:active {
box-shadow: 3px 3px 10px 0 #111111;
width: 51px;
height: 25px;
}
#big_Img {
border: 1px solid #ccc;
width: 900px;
height: 450px;
}
#img {
pointer-events: none;
user-select: none;
-webkit-user-drag: none;
width: 900px;
height: 450px;
margin: auto;
position: relative;
}
.guanbi {
position: absolute;
left: 0px;
bottom: 0px;
height: 21px;
width: 20px;
border-radius: 50%;
background-color: #0f0f0f;
color: #fff;
text-align: center;
line-height: 20px;
cursor: pointer;
}
.el-form-item{
width: 380px !important;
}
.el-form-item__content {
width: 200px !important;
}
.el-form-item__label{
width: 140px !important;
}
</style>
<body>
<div id="main" style="display: flex;padding: 20px;" v-cloak>
<div style="width: 70%;">
<el-form label-width="160px" inline>
<el-form-item label="选择菜单">
<el-select v-model="menu" clearable placeholder="请选择菜单">
<el-option v-for="item in pageData" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="选择对应功能">
<el-select v-model="Feature" clearable placeholder="请选择对应功能">
<el-option v-for="item in featureData" :key="item.value" :label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-button type="primary" @click="dialogVisible = true">添加功能</el-button>
<el-button type="primary" @click="removeMenu">删除功能</el-button>
<br>
<el-form-item label="引导语">
<el-input v-model="yindaoyu" style="width: 200px;" clearable></el-input>
</el-form-item>
<el-form-item label="请拖拽右侧至图片区域">
<div draggable="true" id="source" class="drag"></div>
</el-form-item>
<el-button @click="caozuo()">预览效果</el-button>
<el-button type="primary" @click="save">保存</el-button>
</el-form>
<h3 style="color: #000;font-size: 20px">图片区域</h3>
<div id="big_Img" class="v2" draggable="true">
<div id="img"></div>
</div>
</div>
<div style="height: 148px">
<el-upload
v-model="dialogImageUrl"
action="/wyservice/workquery/uploadImg"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:limit="1"
:on-exceed="handleExceed"
:on-success="((response, file, fileList)=>handleSuccess(response, file, fileList,'imgs'))"
:on-remove="handleRemove">
<div style="text-align: center"><img style="margin-bottom: 20px" src="/img/lushang/upload_pictures.png"/>
<p style="margin-top: -110px;">点击上传图片</p></div>
</el-upload>
<el-dialog :visible.sync="dialogVisible1">
<img width="100%" :src="dialogImageUrl" alt="">
</el-dialog>
</div>
<!-- 添加框 -->
<el-dialog title="添加功能按钮" :visible.sync="dialogVisible" width="30%">
<el-form :model="formInline" class="demo-form-inline" label-width="160px">
<el-form-item label="选择菜单">
<el-select v-model="formInline.formPage" clearable placeholder="请选择菜单">
<el-option v-for="item in pageData" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="输入功能标题">
<el-input v-model="formInline.featureTitle" clearable></el-input>
</el-form-item>
<el-form-item label="输入功能描述">
<el-input v-model="formInline.featureContent" clearable></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="btnAdd">确定</el-button>
</el-form-item>
</el-form>
</el-dialog>
</div>
<div th:include="include :: footer"></div>
<div th:include="include::load"></div>
<!-- 引入操作引导组件 -->
<script th:src="@{/js/intro/intro.js}"></script>
<script th:src="@{/js/intro/introComponent.js}"></script>
<script th:src="@{/js/appjs/operationGuidance/operationGuidance.js}"></script>
</body>
</html>
let vm = new Vue({
el: '#main',
data: {
dialogImageUrl: 'https://t7.baidu.com/it/u=4198287529,2774471735&fm=193&f=GIF',
dialogVisible1: false,
dialogVisible: false,
yindaoyu: '',
formInline: {
formPage: '',
featureTitle: '',
featureContent: '',
},
menu: '',
Feature: '',
pageData: [],
featureData: [],
tag_number: 0,
mobileDiv: false,
move_x: '',
move_y: '',
lt: '',
ls: '',
},
mounted() {
//拖拽元素绘制div
source.ondragend = ev => {
if (this.yindaoyu) {
this.draw(ev)
} else {
layer.msg("请先输入引导语")
}
}
},
methods: {
//确定添加
btnAdd() {
this.dialogVisible = false
},
//删除功能
removeMenu(){
},
//点击保存
save() {
let htmlCode = document.getElementById('big_Img').outerHTML;
console.log(htmlCode);
},
draw(ev) {
var oBox = document.getElementById("big_Img");
ev = window.event || ev;
var move_x1, move_y1, move_x2, move_y2, move_x3, move_y3, move_x4, move_y4;
var isMove = false;
var angle_x;
var x1 = ev.clientX - 130 - oBox.offsetLeft; //起始坐标,130是左边导航的宽度
var y1 = ev.clientY - 30 - oBox.offsetTop;
var x2 = x1 + 100;
var y2 = y1;
var x4 = x1;
var y4 = y1 + 100;
var x3 = ev.clientX - 80 - oBox.offsetLeft + 50; //终点坐标
var y3 = ev.clientY + 20 - oBox.offsetTop + 50;
var degree = 0;
var oDiv = document.createElement("div");
// var otext = document.createElement("div1");
// oBox.appendChild(otext);
oBox.appendChild(oDiv);
// otext.style.left = x1 - 15 + "px"; //在绘制的大div左上角的位置
// otext.style.top = y1 - 15 + "px";
//15是要减去小div的大小,这样位置才会在div的外面
this.tag_number++;
// otext.id = this.tag_number;
// otext.innerText = this.tag_number;
//在绘制的div1中加上文本,这里是加上左上角的编号
oDiv.style.left = (x3 > x1 ? x1 : x3) + "px"; //绘制oDiv的样式
oDiv.style.top = (y3 > y1 ? y1 : y3) + "px";
oDiv.style.width = Math.abs(x3 - x1) + "px";
oDiv.style.height = Math.abs(y3 - y1) + "px";
oDiv.id = this.tag_number
oDiv.setAttribute("class", "requ");
oDiv.setAttribute("data-step", this.tag_number); //添加指引步骤从0开始,如果用户删除中间的,也不会影响
oDiv.setAttribute("data-intro", this.yindaoyu); //这里是用户输入的引导语
oDiv.innerHTML = "<div class=" + "guanbi" + ">x<div>" //关闭按钮
let guan = oDiv.children[0]
guan.onclick = ev => { //鼠标点击
oDiv.remove()
}
oDiv.onmousedown = ev => {
var down_x1 = ev.clientX; //获取鼠标按下的坐标
var down_y1 = ev.clientY;
//获取元素的left,top值
var l = oDiv.offsetLeft;
var t = oDiv.offsetTop;
if (ev.offsetX < oDiv.clientWidth - 10 && ev.offsetY < oDiv.clientHeight - 10) { //判断是点击的右下角改变大小还是移动div
this.mobileDiv = true //移动div
} else {
this.mobileDiv = false //改变大小
}
document.onmousemove = ev => { //鼠标移动事件
var ev = ev || event;
//获取鼠标移动时的坐标
var down_x2 = ev.clientX;
var down_y2 = ev.clientY;
//计算出鼠标的移动距离
this.move_x = down_x2 - down_x1;
this.move_y = down_y2 - down_y1;
//移动的数值与元素的left,top相加,得出元素的移动的距离
this.lt = this.move_y + t;
this.ls = this.move_x + l;
if (this.mobileDiv == true) { //移动div
//更改元素的left,top值
oDiv.style.top = this.lt + 'px';
oDiv.style.left = this.ls + 'px';
} else {
//改变div大小开始
//获取鼠标移动时的坐标
var down_x2 = ev.clientX;
var down_y2 = ev.clientY;
}
}
document.onmouseup = ev => { //鼠标抬起
document.onmousemove = null;
if (this.mobileDiv == true) {
// otext.style.top = this.lt - 15 + 'px'; //随着大div的移动而移动
// otext.style.left = this.ls - 15 + 'px';
} else {
//改变大小的鼠标抬起
}
}
}
},
caozuo() {
introJs('.v2').setOptions({
nextLabel: "下一步", // 下一个的按钮文字
prevLabel: "上一步", // 上一个按钮文字
// skipLabel: "跳过", // 跳过指引的按钮文字
doneLabel: "完成", // 完成按钮的文字
hidePrev: false, // 是否在第一步中隐藏“上一步”按钮;不隐藏,将呈现为一个禁用的按钮
hideNext: false, // 是否在最后一步中隐藏“下一步”按钮(同时会隐藏完成按钮);不隐藏,将呈现为一个禁用的按钮
exitOnEsc: true, // 点击键盘的ESC按钮是否退出指引
exitOnOverlayClick: false, // 点击遮罩层时是否退出介绍
showStepNumbers: false, // 是否显示步骤编号
disableInteraction: true, // 是否禁用高亮显示框内元素的交互
showBullets: true, // 是否显示面板的指示点
overlayOpacity: 0.7, // 遮罩层的透明度 0-1之间
helperElementPadding: 10, // 选中的指引元素周围的填充距离
showProgress: true, //进度条
}).start();
},
handleRemove(file, fileList) {
let url = this.dialogImageUrl.split('http://geecity-image.oss-cn-qingdao.aliyuncs.com/')
$.ajax({
type: "POST",
url: "/workquery/gdlist/delimg",
data: {filename: url[1]},
error: function (request) {
parent.layer.alert("Connection error");
},
success: (data) => {
this.dialogImageUrl = ''
$("#img").css({
"background": "url("+this.dialogImageUrl+")" + ""+ "no-repeat",
});
layer.msg("删除成功")
}
})
},
handlePictureCardPreview(file) {
this.dialogImageUrl = file.url;
this.dialogVisible1 = true;
},
handleSuccess(response, file, fileList, type) {
this.dialogImageUrl = response.data.src
$("#img").css({
"background": "url("+this.dialogImageUrl+")" + ""+ "no-repeat",
"background-size": "950px"
});
},
handleExceed() {
return layer.msg("最多上传1张图片")
}
},
})
introComponent文件
function intro_lq(yindaoList) {
let setOptions = {
nextLabel: "下一步", // 下一个的按钮文字
prevLabel: "上一步", // 上一个按钮文字
// skipLabel: "跳过", // 跳过指引的按钮文字
doneLabel: "完成", // 完成按钮的文字
hidePrev: false, // 是否在第一步中隐藏“上一步”按钮;不隐藏,将呈现为一个禁用的按钮
hideNext: false, // 是否在最后一步中隐藏“下一步”按钮(同时会隐藏完成按钮);不隐藏,将呈现为一个禁用的按钮
exitOnEsc: true, // 点击键盘的ESC按钮是否退出指引
exitOnOverlayClick: false, // 点击遮罩层时是否退出介绍
showStepNumbers: false, // 是否显示步骤编号
disableInteraction: true, // 是否禁用高亮显示框内元素的交互
showBullets: true, // 是否显示面板的指示点
overlayOpacity: 0.7, // 遮罩层的透明度 0-1之间
helperElementPadding: 10, // 选中的指引元素周围的填充距离
showProgress: true, //进度条
steps: yindaoList
}
return setOptions
}
结束
这里面代码都很详细啦,大家看一遍就懂了,如果不懂可以评论哦,回复超快