<template>
|
<div :style="canvasStyle" class="canvas-container" @mousedown="selectWidget=null;">
|
<div class="canvas-content">
|
<draggable v-selectable="data.fields" v-model="data.fields" :options="{group:'people', ghostClass: 'ghost', filter:'.detail-table'}" class="drag-list" @end="handleMoveEnd" @add="handleWidgetAdd">
|
<template v-for="(element, index) in data.fields">
|
<DraggableResizable v-bind.sync="element.options" :key="index" :class="{'active': isActive(element)}" :style="'z-index:' + element.options.z" :data-key="element.key" class="resable-item" @click.native.stop="handleSelectWidget(index)">
|
<template v-if="element.type=='table'">
|
<table :style="{'border-color':element.borderColor, 'width':element.width}" class="detail-table" @mousedown.stop>
|
<thead v-if="element.showHeader">
|
<draggable v-model="element.fields" :options="{group:'detail', ghostClass: 'ghost', animation:300, chosenClass:'chosen-item'}" :class="{'drag-th':true}" tag="tr">
|
<th v-for="(th, thIndex) in element.fields" :key="thIndex" :style="{'border-color':element.borderColor, 'width':th.width+'px'}" :class="{'drag-active': isActive(th)}" @click.stop="handleThSelectWidget(th)">
|
<div :style="getHeaderDivStyle(th)" class="cell">{{ th.label }}</div>
|
<div class="tool">
|
<el-button title="删除" class="widget-action-delete" circle plain type="danger" @click.stop="handleDetailWidgetDelete(element.fields, thIndex)">
|
<i class="el-icon-yrt-shanchu2"></i>
|
</el-button>
|
</div>
|
</th>
|
</draggable>
|
</thead>
|
<tbody>
|
<tr v-for="i in [0]" :key="'tr'+i">
|
<td v-for="(th, thIndex) in element.fields" :key="thIndex" :style="{'border-color':element.borderColor}">
|
<div class="cell"></div>
|
</td>
|
</tr>
|
</tbody>
|
</table>
|
<div class="tool">
|
<el-button title="删除" class="widget-action-delete" circle plain type="danger" @click.stop="handleWidgetDelete(index)">
|
<i class="el-icon-yrt-shanchu2"></i>
|
</el-button>
|
<el-button title="添加字段" class="widget-action-addplus" circle plain type="primary" @click.stop="handleDetailWidgetAdd(element, index)">
|
<i class="el-icon-yrt-addplus"></i>
|
</el-button>
|
</div>
|
</template>
|
<template v-else-if="element.type=='field'">
|
<div :style="element.styles" :class="{'drag-active': isActive(element)}" class="text-box">
|
<div :style="element.label.styles" class="label">{{ element.label.title }}
|
<template v-if="element.label.showColon">:</template>
|
</div>
|
<div :style="element.text.styles" class="text">{ {{ element.text.prop }} }</div>
|
</div>
|
<div class="tool">
|
<el-button v-if="element.options.w>50" title="删除" class="widget-action-delete" circle plain type="danger" @click.stop="handleWidgetDelete(index)">
|
<i class="el-icon-yrt-shanchu2"></i>
|
</el-button>
|
<!-- <el-button title="复制" class="widget-action-clone" circle plain type="primary" @click.stop="handleWidgetClone(index)">
|
<i class="el-icon-yrt-fuzhi4"></i>
|
</el-button> -->
|
</div>
|
</template>
|
<template v-else-if="element.type=='barcode'">
|
<div :style="element.styles" :class="{'drag-active': isActive(element)}" class="text-box">
|
<vue-barcode :value="element.barcode.value" :options="element.barcode.options"></vue-barcode>
|
</div>
|
<div class="tool">
|
<el-button v-if="element.options.w>50" title="删除" class="widget-action-delete" circle plain type="danger" @click.stop="handleWidgetDelete(index)">
|
<i class="el-icon-yrt-shanchu2"></i>
|
</el-button>
|
<!-- <el-button title="复制" class="widget-action-clone" circle plain type="primary" @click.stop="handleWidgetClone(index)">
|
<i class="el-icon-yrt-fuzhi4"></i>
|
</el-button> -->
|
</div>
|
</template>
|
<template v-else-if="element.type=='qrcode'">
|
<div :style="element.styles" :class="{'drag-active': isActive(element)}" class="text-box">
|
<vue-qrcode :value="element.qrOptions.value" :options="element.qrOptions"></vue-qrcode>
|
</div>
|
<div class="tool">
|
<el-button v-if="element.options.w>50" title="删除" class="widget-action-delete" circle plain type="danger" @click.stop="handleWidgetDelete(index)">
|
<i class="el-icon-yrt-shanchu2"></i>
|
</el-button>
|
</div>
|
</template>
|
<!-- 图片上传 -->
|
<template v-else-if="element.type=='image'">
|
<div :style="element.styles" :class="{'drag-active': isActive(element)}" class="text-box">
|
<el-upload :action="common.domain + element.image.serverAction" :show-file-list="false" :http-request="(params) => {uploadHttp(params, element)}" :before-upload="handleBeforeUpload" :headers="headers" :style="{width:element.image.width+'mm', height:element.image.height+'mm'}" class="img-uploader">
|
<img v-if="element.image.imageUrl" :src="element.image.imageUrl" :style="{width:element.image.width+'mm', height:element.image.height+'mm'}" class="img">
|
<i v-else class="el-icon-plus img-uploader-icon"></i>
|
</el-upload>
|
</div>
|
<div class="tool">
|
<el-button v-if="element.options.w>50" title="删除" class="widget-action-delete" circle plain type="danger" @click.stop="handleWidgetDelete(index)">
|
<i class="el-icon-yrt-shanchu2"></i>
|
</el-button>
|
<!-- <el-button title="复制" class="widget-action-clone" circle plain type="primary" @click.stop="handleWidgetClone(index)">
|
<i class="el-icon-yrt-fuzhi4"></i>
|
</el-button> -->
|
</div>
|
</template>
|
<template v-else-if="['line-horizontal', 'line-vertical'].indexOf(element.type)>=0">
|
<div :style="element.styles" :class="{'drag-active': isActive(element)}" class="text-box">
|
</div>
|
<div class="tool">
|
<el-button v-if="element.options.w>50" title="删除" class="widget-action-delete" circle plain type="danger" @click.stop="handleWidgetDelete(index)">
|
<i class="el-icon-yrt-shanchu2"></i>
|
</el-button>
|
<!-- <el-button title="复制" class="widget-action-clone" circle plain type="primary" @click.stop="handleWidgetClone(index)">
|
<i class="el-icon-yrt-fuzhi4"></i>
|
</el-button> -->
|
</div>
|
</template>
|
<template v-else-if="['pagination'].indexOf(element.type)>=0">
|
<div :style="element.styles" :class="{'drag-active': isActive(element)}" class="pagination-box">
|
<div class="label">{{ element.layout }}</div>
|
</div>
|
<div class="tool">
|
<el-button v-if="element.options.w>50" title="删除" class="widget-action-delete" circle plain type="danger" @click.stop="handleWidgetDelete(index)">
|
<i class="el-icon-yrt-shanchu2"></i>
|
</el-button>
|
<!-- <el-button title="复制" class="widget-action-clone" circle plain type="primary" @click.stop="handleWidgetClone(index)">
|
<i class="el-icon-yrt-fuzhi4"></i>
|
</el-button> -->
|
</div>
|
</template>
|
<template v-else>
|
<div :style="element.styles" :class="{'drag-active': isActive(element)}" class="text-box">
|
<div :style="element.label.styles" class="label">{{ element.label.title }}</div>
|
</div>
|
<div class="tool">
|
<el-button v-if="element.options.w>50" title="删除" class="widget-action-delete" circle plain type="danger" @click.stop="handleWidgetDelete(index)">
|
<i class="el-icon-yrt-shanchu2"></i>
|
</el-button>
|
<!-- <el-button title="复制" class="widget-action-clone" circle plain type="primary" @click.stop="handleWidgetClone(index)">
|
<i class="el-icon-yrt-fuzhi4"></i>
|
</el-button> -->
|
</div>
|
</template>
|
</DraggableResizable>
|
</template>
|
</draggable>
|
</div>
|
</div>
|
</template>
|
|
<script>
|
import { selectable } from "./directive/selectable.js";
|
import Draggable from "vuedraggable";
|
import DraggableResizable from "@/components/base/draggable-resizable";
|
import VueBarcode from "@xkeshi/vue-barcode";
|
import VueQrcode from "@chenfengyuan/vue-qrcode";
|
import ossClient from "@/utils/aliyun.oss.client";
|
|
export default {
|
components: {
|
Draggable,
|
DraggableResizable,
|
VueBarcode,
|
VueQrcode
|
},
|
directives: {
|
selectable
|
},
|
props: {
|
data: {
|
type: Object,
|
default: null
|
},
|
select: {
|
type: Object,
|
default: null
|
},
|
configType: {
|
type: String,
|
default: null
|
},
|
detailFields: {
|
type: Array,
|
default: null
|
}
|
},
|
data() {
|
return {
|
selectWidget: this.select
|
};
|
},
|
computed: {
|
currentConfigType: {
|
get: function() {
|
return this.configType;
|
},
|
set: function(val) {
|
this.$emit("update:config-type", val);
|
}
|
},
|
// 当前选中明细列表
|
currentDetailFields: {
|
get: function() {
|
return this.detailFields;
|
},
|
set: function(val) {
|
this.$emit("update:detail-fields", val);
|
}
|
},
|
// 获得当前是否激活
|
isActive: function() {
|
return function(th) {
|
var active = this.selectWidget && this.selectWidget.key === th.key;
|
return active;
|
};
|
},
|
// 画布样式设置
|
canvasStyle: function() {
|
var opts = this.data.dataOptions;
|
return {
|
width: opts.width + "mm",
|
height: opts.height + "mm",
|
"padding-top": opts.paddingTop + "mm",
|
"padding-bottom": opts.paddingBottom + "mm",
|
"padding-left": opts.paddingLeft + "mm",
|
"padding-right": opts.paddingRight + "mm"
|
};
|
},
|
// 上传header参数
|
headers: function() {
|
var timestamp = new Date().valueOf();
|
var nonce = Math.random()
|
.toString(36)
|
.substr(2);
|
var guid = this.common.getUserGuid();
|
var userInfo = this.common.getUserInfo();
|
var accessToken = userInfo.accessToken;
|
var tokenInfo = this.common.getTokenInfo();
|
var params = {
|
noDataSign: true
|
};
|
|
var signature = this.common.getSignature(timestamp, nonce, tokenInfo, params);
|
var opts = {
|
timestamp: timestamp,
|
nonce: nonce,
|
guid: guid,
|
accessToken: accessToken,
|
signature: signature,
|
subFolder: "print", // 设置子文件夹
|
noDataSign: true // 数据不参与加密
|
};
|
return opts;
|
}
|
},
|
watch: {
|
select(val) {
|
this.selectWidget = val;
|
},
|
selectWidget: {
|
handler(val) {
|
this.$emit("update:select", val);
|
},
|
deep: true
|
},
|
data: {
|
handler(val) {},
|
deep: true
|
}
|
},
|
methods: {
|
handleMoveEnd({ newIndex, oldIndex }) {},
|
handleMoveStart({ oldIndex }) {},
|
// 元素选中
|
handleSelectWidget(index) {
|
this.selectWidget = this.data.fields[index];
|
this.currentConfigType = "WidgetConfig";
|
},
|
// 明细单元格选中
|
handleThSelectWidget(th, index) {
|
this.selectWidget = th;
|
this.currentConfigType = "WidgetConfig";
|
},
|
handleWidgetAdd(evt) {
|
const newIndex = evt.newIndex;
|
|
// 为拖拽到容器的元素添加唯一 key
|
const key = Date.parse(new Date()) + "_" + Math.ceil(Math.random() * 99999);
|
var newField = JSON.parse(JSON.stringify(this.data.fields[newIndex]));
|
this.$set(this.data.fields, newIndex, { ...newField, key });
|
delete this.data.fields[newIndex].icon;
|
|
if (this.data.fields[newIndex].type === "table") {
|
var _fields = this.data.fields[newIndex].fields;
|
_fields.forEach((_field, _index) => {
|
const _key = Date.parse(new Date()) + "_" + Math.ceil(Math.random() * 99999) + _index;
|
this.$set(_field, "key", _key);
|
});
|
}
|
|
this.currentConfigType = "WidgetConfig";
|
this.selectWidget = this.data.fields[newIndex];
|
},
|
// 删除
|
handleWidgetDelete(index) {
|
if (this.data.fields.length - 1 === index) {
|
if (index === 0) {
|
this.selectWidget = {};
|
} else {
|
this.selectWidget = this.data.fields[index - 1];
|
}
|
} else {
|
this.selectWidget = this.data.fields[index + 1];
|
}
|
|
this.$nextTick(() => {
|
this.data.fields.splice(index, 1);
|
});
|
},
|
// 复制
|
handleWidgetClone(index) {
|
const cloneData = {
|
...JSON.parse(JSON.stringify(this.data.fields[index])),
|
key: Date.parse(new Date()) + "_" + Math.ceil(Math.random() * 99999)
|
};
|
cloneData.options.x += 5;
|
cloneData.options.y += 5;
|
|
this.data.fields.splice(index, 0, cloneData);
|
|
this.$nextTick(() => {
|
this.selectWidget = this.data.fields[index + 1];
|
});
|
},
|
// 删除明细字段
|
handleDetailWidgetDelete(fields, index) {
|
if (fields.length - 1 === index) {
|
if (index === 0) {
|
this.selectWidget = {};
|
} else {
|
this.selectWidget = fields[index - 1];
|
}
|
} else {
|
this.selectWidget = fields[index + 1];
|
}
|
|
this.$nextTick(() => {
|
fields.splice(index, 1);
|
});
|
},
|
// 新增明细字段
|
handleDetailWidgetAdd(element, index) {
|
this.$set(element.fields, element.fields.length, {
|
type: "detail-field",
|
prop: "",
|
width: 150,
|
header: {
|
fontSize: 14,
|
textAlign: "center",
|
lineHeight: 20
|
},
|
body: {
|
fontSize: 14,
|
textAlign: "left",
|
lineHeight: 20
|
},
|
label: "字段" + element.fields.length,
|
key: Date.parse(new Date()) + "_" + Math.ceil(Math.random() * 99999)
|
});
|
},
|
// 表格按下事件
|
onThMouseDown(field, $event) {},
|
// 上传至阿里云
|
uploadHttp(params, element) {
|
const fileName = `upload/print/${this.common.formatDate(new Date(), "YYYY-MM-DD")}/${new Date().valueOf()}-${this.common.getGUID()}.jpg`; // 定义唯一的文件名
|
ossClient(this.common.uploadConf)
|
.put(fileName, params.file, {
|
ContentType: "image/jpeg"
|
})
|
.then(({ res, url, name }) => {
|
if (res && res.status === 200) {
|
element.image.imageUrl = res.requestUrls[0];
|
}
|
})
|
.catch(err => {
|
this.$message.error(`上传图片失败` + err.message);
|
});
|
},
|
// 上传图片前
|
handleBeforeUpload(file) {
|
const isImg = ["image/jpeg", "image/png"].indexOf(file.type) >= 0;
|
const isLt2M = file.size / 1024 / 1024 < 2;
|
|
if (!isImg) {
|
this.$message.error("上传头像图片只能是 JPG/PNG 格式!");
|
}
|
if (!isLt2M) {
|
this.$message.error("上传头像图片大小不能超过 2MB!");
|
}
|
return isImg && isLt2M;
|
},
|
// 表格头部div样式
|
getHeaderDivStyle(th) {
|
const data = { "font-size": th.header.fontSize + "mm", "line-height": th.header.lineHeight + "mm", "text-align": th.header.textAlign };
|
if (th.width) {
|
data.width = th.width + "px";
|
}
|
return data;
|
}
|
}
|
};
|
</script>
|
|
<style lang="scss" scoped>
|
.canvas-container {
|
-moz-box-shadow: 0px 0px 10px #3d3d3d;
|
-webkit-box-shadow: 0px 0px 10px #3d3d3d;
|
box-shadow: 0px 0px 10px #3d3d3d;
|
margin: 20px 6px;
|
.canvas-content {
|
height: 100%;
|
padding-bottom: 0px;
|
border: 1px dotted #000000;
|
.drag-list {
|
height: 100%;
|
position: relative;
|
}
|
|
.resable-item {
|
&.active {
|
border: 1px dashed #888;
|
}
|
&.selected {
|
border: 1px dashed #888;
|
}
|
}
|
.tool {
|
display: none;
|
position: absolute;
|
right: 0;
|
bottom: -30px;
|
z-index: 100;
|
}
|
.active {
|
background-color: rgb(213, 234, 255);
|
> .tool {
|
display: block;
|
}
|
}
|
.text-box {
|
overflow: hidden;
|
white-space: nowrap;
|
.label {
|
width: 120px;
|
display: inline-block;
|
text-align: right;
|
}
|
.text {
|
display: inline-block;
|
}
|
}
|
}
|
|
.ghost {
|
background: #fff;
|
border: 1px dashed #409eff;
|
|
&::after {
|
background: #fff;
|
}
|
}
|
|
li.ghost {
|
height: 30px;
|
list-style: none;
|
font-size: 0;
|
}
|
|
.detail-table {
|
border-collapse: collapse;
|
border: 1px solid #dcdfe6;
|
background-color: white;
|
th,
|
td {
|
font-weight: normal;
|
position: relative;
|
border-collapse: collapse;
|
border: 1px solid #dcdfe6;
|
padding: 3px 2px;
|
.cell {
|
min-height: 20px;
|
}
|
&.drag-active {
|
background-color: rgb(213, 234, 255);
|
.tool {
|
display: block;
|
}
|
}
|
}
|
}
|
|
// 选择框
|
/deep/ .selectable {
|
position: absolute;
|
width: 0px;
|
height: 0px;
|
background-color: #409eff20;
|
border: 1px solid #409eff70;
|
}
|
|
// 图片上传
|
/deep/ .img-uploader {
|
.el-upload {
|
border: 1px dashed #d9d9d9;
|
border-radius: 6px;
|
cursor: pointer;
|
position: relative;
|
overflow: hidden;
|
&:hover {
|
border-color: #409eff;
|
}
|
}
|
.img-uploader-icon {
|
font-size: 28px;
|
color: #8c939d;
|
width: 100px;
|
height: 60px;
|
line-height: 60px;
|
text-align: center;
|
}
|
.img {
|
width: 100px;
|
height: 60px;
|
display: block;
|
}
|
}
|
}
|
</style>
|