1、安装vuedraggable依赖
npm i vuedraggable
2、在页面中引入
import Draggable from 'vuedraggable'
components: {
Draggable
},
下面为实图使用,代码直接参考下面的,拷贝即可使用
//主页面
<template>
<div class="ctnr">
<div class="station-area">
<div class="text-area">
<el-button v-if="disabledDrag && section.length < 4" type="text" @click="changeAdd">新建
</el-button>
<div class="edit-area">
<el-button v-if="disabledDrag" type="text" @click="changeDrag">编辑
</el-button>
<div v-else>
<el-button type="text" @click="changeDrag('updateList')">完成</el-button>
<el-button type="text" @click="changeDrag('clearList')">取消</el-button>
</div>
</div>
</div>
</div>
<draggable :disabled="disabledDrag" class="box" v-model="section" @start="onStartBlock" @end="onEndBlock"
animation="300">
<transition-group>
<div class="section" v-for="(item,index) in section" :key="index">
<div class="section-card">
<h1 class="card-title">
<el-input class="title-inp" v-if="!disabledDrag" v-model="item.name"></el-input>
<span v-else>{{item.name}}</span>
<i v-if="!disabledDrag" class="el-icon-circle-plus i-plus"
@click="bindAddSuit(item.id,item.suites.length)"></i>
</h1>
<draggable :disabled="disabledDrag" class="card-box" v-model="item.suites" @start="onStartSuite(item.id)"
@end="onEndSuite(item.id)" animation="300">
<transition-group>
<div class="item" v-for="(item2,index2) in item.suites" :key="index2">
<div class="item-card" :style="'background:'+item2.color" :class="{'href-type':disabledDrag}"
@click="goPreview(item2)">
<div class="op-box" v-if="!disabledDrag">
<i class="el-icon-edit" @click="bindEditSuit(item2,item.id)"></i>
<i class="el-icon-delete" @click="bindRemoveSuit(item2.id)"></i>
</div>
<span class="card-name">{{item2.name}}</span>
</div>
</div>
</transition-group>
</draggable>
</div>
</div>
</transition-group>
</draggable>
<suit-box ref="SuitBox" @refreshList="refreshList"></suit-box>
</div>
</template>
<script>
import Draggable from 'vuedraggable'
import SuitBox from '@/components/CommonForm/SuitBox'
export default {
data() {
return {
disabledDrag: true,
oSection: [],
section: [],
isUpdateBlock: false,
isUpdateSuit: [],
};
},
components: {
Draggable,
SuitBox
},
watch: {
section: {
handler(newVal, oldVal) {
oldVal.length > 0 && (this.isUpdateBlock = true)
},
deep: true
}
},
computed: {},
mounted() {
this.getBlockList()
},
created() {},
methods: {
getBlockList() {
var data = [
{
"create_time": "2022-04-01 15:12:33",
"id": 2,
"modify_time": "2022-06-29 19:44:23",
"name": "测试区域2133213122355",
"order_num": 1,
"suites": [
{
"color": "#A47373",
"create_time": "2022-04-01 17:48:32",
"fillable": false,
"id": 4,
"modify_time": "2022-07-29 17:55:52",
"name": "日常签到",
"order_num": 1,
"template_id": 423
},
{
"color": "#9013FE",
"create_time": "2022-10-09 11:01:02",
"fillable": true,
"id": 22,
"modify_time": "2022-10-09 11:01:02",
"name": "接口",
"order_num": 2,
"template_id": 369
}
]
},
{
"create_time": "2022-04-01 15:12:41",
"id": 3,
"modify_time": "2022-07-29 17:38:01",
"name": "防冻液防冻液111",
"order_num": 2,
"suites": []
},
{
"create_time": "2022-06-28 15:16:56",
"id": 41,
"modify_time": "2022-07-27 14:06:52",
"name": "额3v",
"order_num": 3,
"suites": [
{
"color": "#F9F115",
"create_time": "2022-07-27 14:07:02",
"fillable": false,
"id": 18,
"modify_time": "2022-07-27 14:07:02",
"name": "43关闭",
"order_num": 1,
"template_id": 407
}
]
},
{
"create_time": "2022-06-28 15:16:52",
"id": 36,
"modify_time": "2022-09-05 11:31:14",
"name": "供电局",
"order_num": 4,
"suites": [
{
"color": "#BD10E0",
"create_time": "2022-07-27 14:08:20",
"fillable": true,
"id": 19,
"modify_time": "2022-07-27 14:08:20",
"name": "看给客户",
"order_num": 1,
"template_id": 369
}
]
},
{
"create_time": "2022-06-28 15:16:56",
"id": 42,
"modify_time": "2022-06-29 19:44:23",
"name": "",
"order_num": 5,
"suites": []
},
{
"create_time": "2022-06-28 15:16:52",
"id": 39,
"modify_time": "2022-06-29 19:44:23",
"name": "",
"order_num": 6,
"suites": []
},
{
"create_time": "2022-06-28 15:16:56",
"id": 40,
"modify_time": "2022-08-25 17:05:46",
"name": "saca",
"order_num": 7,
"suites": [
{
"color": "#CB2929",
"create_time": "2022-08-25 17:05:52",
"fillable": false,
"id": 21,
"modify_time": "2022-08-25 17:05:52",
"name": "sacas",
"order_num": 1,
"template_id": 423
}
]
},
{
"create_time": "2022-06-28 15:16:52",
"id": 37,
"modify_time": "2022-06-29 19:44:23",
"name": "",
"order_num": 8,
"suites": []
},
{
"create_time": "2022-04-01 15:12:25",
"id": 1,
"modify_time": "2022-06-29 19:44:23",
"name": "时分为非就不能用鼠标全选",
"order_num": 9,
"suites": [
{
"color": "#F8E71C",
"create_time": "2022-04-01 16:35:25",
"fillable": false,
"id": 2,
"modify_time": "2022-04-01 17:59:12",
"name": "套件2",
"order_num": 2,
"template_id": 363
},
{
"color": "#B33434",
"create_time": "2022-04-01 18:42:04",
"fillable": true,
"id": 6,
"modify_time": "2022-04-01 18:42:04",
"name": "卡片222",
"order_num": 2,
"template_id": 298
},
{
"color": "#DC17DC",
"create_time": "2022-04-22 14:21:48",
"fillable": false,
"id": 10,
"modify_time": "2022-04-24 18:27:38",
"name": "vr",
"order_num": 3,
"template_id": 372
}
]
},
{
"create_time": "2022-06-28 15:16:56",
"id": 43,
"modify_time": "2022-06-29 19:44:23",
"name": "",
"order_num": 10,
"suites": []
},
{
"create_time": "2022-06-28 15:16:52",
"id": 38,
"modify_time": "2022-06-29 19:44:23",
"name": "",
"order_num": 11,
"suites": []
},
{
"create_time": "2022-04-01 15:12:48",
"id": 4,
"modify_time": "2022-06-29 19:44:23",
"name": "测试区域4",
"order_num": 12,
"suites": [
{
"color": "#994545",
"create_time": "2022-04-02 10:19:00",
"fillable": true,
"id": 8,
"modify_time": "2022-04-02 10:19:00",
"name": "车控室",
"order_num": 1,
"template_id": 350
}
]
}
]
this.section = data
this.oSection = JSON.parse(JSON.stringify(data))
},
refreshList() {
this.getBlockList()
},
onStartBlock() {
},
onEndBlock(e) {
console.log(e)
this.isUpdateBlock = true
},
onStartSuite() {
},
onEndSuite(blockId) {
// 对那个块中的卡片进行了挪动
!this.isUpdateSuit.includes(blockId) && this.isUpdateSuit.push(blockId)
},
changeAdd() {
for (var i = 0; i < 4; i++) {
let obj = {name: '',order_num: i + 1,station_id: this.station_id}
blockAdd(obj).then(res => {})
}
this.getBlockList();
},
changeDrag(type) {
this.disabledDrag = !this.disabledDrag
switch (type) {
case 'clearList':
this.section = JSON.parse(JSON.stringify(this.oSection))
break;
case 'updateList':
if (this.isUpdateBlock) {
this.updateBlock()
}
if (this.isUpdateSuit.length > 0) {
this.updateSuit()
}
break;
}
},
bindAddSuit(id, orderNum) {
if (this.isUpdateBlock || this.isUpdateSuit.length > 0) {
this.$confirm("区域或卡片有更新是否保存后再新建卡片?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.isUpdateBlock && this.updateBlock()
this.isUpdateSuit.length > 0 && this.updateSuit()
this.$refs.SuitBox.showModal({
block_id: id,
order_num: orderNum + 1
})
}).catch(() => {
this.$refs.SuitBox.showModal({
block_id: id,
order_num: orderNum + 1
})
})
} else {
this.$refs.SuitBox.showModal({
block_id: id,
order_num: orderNum + 1
})
}
},
bindEditSuit(info, id) {
this.$refs.SuitBox.showModal(Object.assign({
block_id: id
}, info), 'edit')
},
updateBlock() {
let data = []
for (const [index, item] of this.section.entries()) {
const obj = {
id: item.id,
name: item.name,
order_num: index + 1
}
data.push(obj)
}
blockModify(data).then(res => {
this.$message.success('区域更新成功')
this.isUpdateBlock = false
})
},
updateSuit() {
const data = []
for (const item of this.isUpdateSuit) {
const nowBlock = this.section.filter(res => res.id === item)[0]
for (const [index2, item2] of nowBlock.suites.entries()) {
const obj = Object.assign(item2, {
block_id: item,
order_num: index2 + 1
})
delete obj.create_time
delete obj.modify_time
data.push(obj)
}
}
suiteUpdate(data).then(res => {
this.$message.success('卡片更新成功')
this.isUpdateSuit = []
})
console.log(data)
},
bindRemoveSuit(id) {
this.$confirm("是否删除此卡片?", "提示", {
confirmButtonText: "确认",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
suiteDelete({
id
}).then(res => {
this.$message.success('卡片删除成功')
this.getBlockList()
})
}).catch(() => {})
},
async goPreview(item) {
if (!this.disabledDrag) return
const detail = await templateDetail({
id: item.template_id
}).then(res => {
return res.data
})
localStorage.previewForm = JSON.stringify(detail.content)
let url = this.$router.resolve({
path: `/preview?type=${item.fillable?'fill':''}&template_id=${item.template_id}`
});
window.open(url.href, '_blank');
}
}
}
</script>
<style lang='scss' scoped>
.ctnr {
height: calc(100vh - 50px);
background: #f6f7f9;
margin: -40px -50px;
padding: 20px 0 20px 20px;
overflow-y: scroll;
}
.station-area {
padding: 0 20px;
display: flex;
align-items: center;
.text-area {
display: flex;
align-items: center;
margin-left: auto;
.edit-area {
margin-left: 15px;
}
}
}
/deep/ .el-form-item--small.el-form-item {
margin-bottom: 0 !important;
}
.box {
width: 100%;
height: 100%;
}
.section {
width: 50%;
height: 50%;
display: inline-block;
padding: 20px 20px 0 0;
&-card {
background: #fff;
border-radius: 14px;
height: 100%;
padding: 20px;
display: flex;
flex-wrap: wrap;
// padding: ;
.card-title {
font-size: 16px;
margin: 0;
position: relative;
width: 100%;
color: #606266;
.title-inp {
width: 50%;
}
.i-plus {
color: #448ef7;
position: absolute;
right: 0;
cursor: pointer;
font-size: 25px;
}
}
.card-box {
width: 100%;
height: calc(100% - 30px);
}
}
.item {
width: 33.3%;
padding: 10px 10px 0 0;
height: 33.3%;
display: inline-block;
&-card {
width: 100%;
height: 100%;
background: #eee;
border-radius: 11px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
&.href-type {
cursor: pointer;
}
.op-box {
position: absolute;
top: 4px;
right: 5px;
color: #fff
}
.card-name {
color: #fff;
font-size: 16px;
width: 80%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
}
}
}
}
</style>
//组件
<template>
<div>
<el-dialog @click.native="bindClosePicker" :title="type==='edit'?'编辑卡片':'添加卡片'" :visible.sync="dialogFormVisible"
:before-close="bindClose">
<el-form :model="form" ref="ruleForm" size="small" :rules="rules" class="form-style">
<el-form-item label="卡片名称" :label-width="formLabelWidth" prop="name">
<el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="选择模板" :label-width="formLabelWidth" prop="template_id">
<el-select v-model="form.template_id" placeholder="请选择">
<el-option v-for="item in templateData" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="选择背景色" :label-width="formLabelWidth" prop="color">
<div class="picker-input" style="position:relative">
<div @click.stop="bindShowPicker" class="color-tip" :style="'background:'+form.color"></div>
<el-input v-model="form.color" autocomplete="off">
</el-input>
</div>
<div class="picker-box" v-if="showPicker" tabindex="0" @click.stop="">
<sketch-picker v-model="pickerColor" @input="updatePickerColor">
</sketch-picker>
</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="bindClose(()=>dialogFormVisible = false)" size="small">取 消</el-button>
<el-button :loading="postLoading" type="primary" @click="bindPost" size="small">确 定</el-button>
</div>
</el-dialog>
<!-- :disableAlpha="true" -->
</div>
</template>
<script>
import {
Sketch
} from 'vue-color'
import {
suiteUpdate
} from '@/api/commonForm'
import {
listTemplate
} from '@/api/form/form'
import {
number
} from '@/utils/validate'
export default {
data() {
const validateNumber = (rule, value, callback) => {
console.log(rule, value, callback)
if (value && !number(value)) {
callback(new Error('排序值输入错误'));
} else {
callback()
}
};
const validateColor = (rule, value, callback) => {
console.log(rule, value, callback)
const reg = /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/
if (!value) {
callback(new Error('请选择色值'));
} else if (value && !reg.test(value)) {
callback(new Error('色值输入错误'));
} else {
callback()
}
};
return {
pickerColor: '',
dialogFormVisible: false,
formLabelWidth: '100px',
form: {
},
type: 'add',
postLoading: false,
templateData: [],
showPicker: false,
rules: {
name: [{
required: true,
message: '请输入卡片名称',
trigger: 'blur'
}],
template_id: [{
required: true,
message: '请选择模板',
trigger: 'change'
}],
color: [{
required: true,
validator: validateColor,
trigger: 'blur'
}],
order_num: [{
validator: validateNumber,
trigger: 'blur'
}],
}
};
},
components: {
'sketch-picker': Sketch,
},
computed: {
},
watch: {},
mounted() {
this.getListTemplate()
},
methods: {
async showModal(info, type) {
this.type = type || 'add'
if (info) {
const {
name,
id,
template_id,
order_num,
block_id,
color
} = info
this.form = {
name,
template_id,
id,
order_num,
block_id,
color: !color ? '' : color
}
} else {
this.form = {}
}
this.dialogFormVisible = true
},
getListTemplate() {
listTemplate({
page: 1,
page_size: 100000
}).then(res => {
this.templateData = res.data
})
},
bindPost() {
console.log(this.form)
this.postLoading = true
this.$refs.ruleForm.validate(async (valid) => {
if (valid) {
this.form.order_num && (this.form.order_num = parseInt(this.form.order_num))
await suiteUpdate([this.form]).then(res => {})
this.postLoading = false
this.$message.success(this.type === 'edit' ? '编辑成功' : '添加成功')
this.bindClose(() => this.dialogFormVisible = false)
this.$emit('refreshList')
} else {
this.postLoading = false
return false
}
});
},
bindClose(done) {
this.pickerColor = ''
this.$refs.ruleForm.resetFields()
done()
},
bindShowPicker() {
this.showPicker = true
},
bindClosePicker() {
this.showPicker = false
},
updatePickerColor(e) {
console.log(e)
this.$set(this.form, 'color', e.hex)
}
}
}
</script>
<style lang='scss' scoped>
/deep/.dialog-footer {
text-align: center;
.el-button {
width: 120px;
}
.el-button+.el-button {
margin-left: 70px;
}
}
.form-style {
width: 70%;
margin: 0 auto;
}
/deep/ .el-select {
width: 100%;
}
.permission-ctnr {
height: 200px;
overflow-y: scroll;
border: 1px solid #DCDFE6;
border-radius: 4px;
}
.picker-box {
position: absolute;
top: 40px;
left: -25%;
}
/deep/.picker-input {
.color-tip {
width: 15px;
height: 15px;
position: absolute;
z-index: 1;
top: 9px;
left: 10px;
border: 1px solid #eeeff2;
border-radius: 3px;
}
.el-input__inner {
padding-left: 30px;
}
}
</style>