实现功能
1、列表标题可更改
2、列表也可以拖动
3、卡片可拖动
4、列表可新增
遇到的问题:
1、dragula 安装导入一直报错, 直接require进来的,css是直接加在了vue文件的最后面
2、v-for 加载ref,导致新增的列表无法让元素拖动
解决方法:
// template
:ref="(el) => setPRef(el, index + 1)"
//方法
let parentRefs = [] as any[];
function setPRef(el: any, index: any) {
if (el && !parentRefs.find((item:any)=>item.id == el.id)) {
parentRefs.push(el);
}
}
//dragula 设置参数
dragula(parentRefs, {
moves: function (el: any, container: any, handle: any) {
return handle.classList.contains("handle2");
},
})
3.列表拖动时,列表需要交换位置
.on('drag', function (el:any,container:any) {
startElement = container;
}).on('drop', function (el:any, container:any) {
if(container.children[0].id == el.id)
startElement.appendChild(container.children[1]);
else
startElement.appendChild(container.children[0]);
})
全部代码
<template>
<div class="contianerkb">
<div class="kanban" id="kanban">
<div class="parentbox" v-for="(item, index) in planList" :key="item" :ref="(el) => setPRef(el, index + 1)" :id="index">
<div class="parent handle2" :id="item.corridorId">
<div class="title handle2">
<el-input
:class="isfocus ? 'focusinput handle2' : 'blurinput handle2'"
@blur="(e) => changeCorridorname(e)"
@keydown.enter="(e) => changeCorridorname(e)"
v-model="item.corridorname"
/>
<el-popover
popper-class="listpopper"
placement="right"
:width="400"
trigger="click"
>
<template #reference>
<em><i class="el-icon-more handle2"></i></em>
</template>
<ul>
<li @click="item.isAddKB = true">添加卡片</li>
<li>添加列表</li>
<li>移动列表</li>
<li>删除列表</li>
</ul>
</el-popover>
</div>
<div :ref="(el) => setRef(el, index + 1)" class="drag handle2 drag--from">
<div
class="box handle"
v-for="(sitem, i) in item.children"
:key="i"
@click="(e) => editKanban(e, sitem.id, item.corridorname)"
>
<el-popover
popper-class="listpopper"
placement="right"
:width="400"
trigger="click"
>
<template #reference>
<em><i class="el-icon-more boxDeal"></i></em>
</template>
<ul>
<li @click="item.isAddKB = true">完成任务</li>
<li>重命名</li>
<li>移动卡片</li>
<li>复制</li>
<li>删除</li>
</ul>
</el-popover>
<div class="content handle">{{ sitem.kbTitle }}</div>
<div class="tagshow handle">
<span
v-for="item in sitem.tagArr"
:key="item.name"
:style="{background:`${item.background}`}"
>
{{ item.name }}
</span>
</div>
</div>
<div class="addKanbanBox">
<el-button
@click="item.isAddKB = true"
v-if="!item.isAddKB"
class="addKanban"
>+</el-button
>
<div v-if="item.isAddKB" class="textareabox">
<el-input
type="textarea"
:rows="2"
placeholder="请输入卡片名称"
v-model="item.addKBTitle"
/>
<span class="corridorAdd" @click="(e) => addKanban(e, index)"
>添加</span
>
<span class="corridorCancel" @click="item.isAddKB = false"
>取消</span
>
</div>
</div>
</div>
<div class="handle2">
<i class="el-icon-rank handle2"></i>
</div>
</div>
</div>
<div class="parent">
<div
class="title"
style="color: #e8453a"
v-if="!isAddCorr"
@click="isAddCorr = true"
>
<em style="font-size: 20px">+</em> 添加列表
</div>
<div v-if="isAddCorr" style="margin-top: 3px">
<el-input class="focusinput" v-model="corridorname" />
<span class="corridorAdd" @click="corridorAdd">添加</span>
<span class="corridorCancel" @click="isAddCorr = false">取消</span>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { ElMessage } from "element-plus";
import { defineComponent, ref, Ref, onMounted, reactive, toRefs } from "vue";
export default defineComponent({
components: { },
setup() {
const state = reactive<any>({
onhold: "计划中",
isfocus: false,
isEditKanban: false,
editContent: null,
planList: [
{
corridorId:"1",
corridorname: "需求池",
children: [
{
picture:'a',
kbTitle: "【指数基金】基础数据查看",
tagArr: [
{ name: "指数基金", background: "#a710f9" },
{ name: "10%", background: "#fe9f9f" },
],
content: "指数基金-数据查看,点击上一步按钮,报错,前端传参交易日期为Invalid date",
id: "1",
},
],
isAddKB: false,
addKBTitle: "",
},
{
corridorId:"2",
corridorname: "需求分析和方案分析",
children: [
{
kbTitle: "【风控中心】权益ETF单次自动校验完成通知(飞书邮件)",
tagArr: [
{ name: "风控中心", background: "#32c845" },
{ name: "50%", background: "#32c8bf" },
],
content: "自动校验-参数管理-修改自动校验参数后,与pcf_base_info基础参数不同步(不一致),且没有标红展示",
id: "2",
},
],
isAddKB: false,
addKBTitle: "",
},
{
corridorId:"3",
corridorname: "开发",
children: [
{
kbTitle: "【开发】",
tagArr: [],
content: "22222222222222222",
id: "3",
},
],
isAddKB: false,
addKBTitle: "",
},
],
refArr: [] as any[],
corridorname: "",
isAddCorr: false,
});
const dragula = require("dragula");
const itemRefs = [] as any[];
function setRef(el: any, index: any) {
if (el) {
itemRefs.push(el);
}
}
let parentRefs = [] as any[];
function setPRef(el: any, index: any) {
if (el && !parentRefs.find((item:any)=>item.id == el.id)) {
parentRefs.push(el);
}
}
// ---------------------------------------点击弹框编辑内容
function editKanban(e: any, id: any, kanbanName: any) {
// console.log(id)
state.editContent = null;
for (let i = 0; i < state.planList.length; i++) {
const item = state.planList[i];
for (let j = 0; j < item.children.length; j++) {
const sitem = item.children[j];
if (sitem.id == id) {
state.editContent = sitem;
break;
}
}
if (state.editContent) {
break;
}
}
}
// initRef(state.planList)
onMounted(() => {
dragFun();
});
let startElement;
let removeChildBtn;
// ---------------------------------------设置拖拽方法
function dragFun() {
dragula(itemRefs, {
moves: function (el: any, container: any, handle: any) {
return handle.classList.contains("handle");
},
}).on('drop', function (el:any, container:any) {
removeChildBtn = container.getElementsByClassName('addKanbanBox')[0];
container.removeChild(removeChildBtn)
container.appendChild(removeChildBtn)
});
dragula(parentRefs, {
moves: function (el: any, container: any, handle: any) {
return handle.classList.contains("handle2");
},
})
.on('drag', function (el:any,container:any) {
startElement = container;
}).on('drop', function (el:any, container:any) {
if(container.children[0].id == el.id)
startElement.appendChild(container.children[1]);
else
startElement.appendChild(container.children[0]);
});
}
// ---------------------------------------更改列表名称
function changeCorridorname(e: any) {
let flag = false;
const repeatList = state.planList.filter(
(item: any) => item.corridorname == e.srcElement.value
);
if (repeatList.length >= 2) {
flag = true;
ElMessage.error("名称不能重复,请知悉");
}
if (flag) {
// 回归到原来的名称
return;
}
console.log("调用接口,改变看板的名称");
}
// ---------------------------------------新增卡片(看板)
function addKanban(e: any, index: any) {
state.planList[index].children.push({
kbTitle: state.planList[index].addKBTitle,
content: "",
id: "4",
tagArr: [],
});
state.planList[index].isAddKB = false;
console.log("调用新增卡片接口");
}
// --------------------------------------- 新增列表
function corridorAdd() {
let flag = false;
const repeatList = state.planList.filter(
(item: any) => item.corridorname == state.corridorname
);
if (repeatList.length >= 1) {
ElMessage.error("名称不能重复,请知悉");
flag = true;
}
if (!flag) {
console.log("调用接口,新增列表");
//
const arr = JSON.parse(JSON.stringify(state.planList));
arr.push({
corridorname: state.corridorname,
children: [],
});
setTimeout(() => {
state.planList.push({
corridorname: state.corridorname,
children: [],
});
}, 100);
}
}
return {
...toRefs(state),
changeCorridorname,
addKanban,
editKanban,
corridorAdd,
setRef,
setPRef,
};
},
});
</script>
<style lang="scss" scoped>
.contianerkb {
width: 100%;
height: calc(100% - 60px);
overflow-x: auto;
}
#kanban {
/deep/ .el-dialog__headerbtn .el-dialog__close {
color: #fff !important;
font-size: 18px !important;
}
font-family: "element-icons";
height: calc(100% - 10px);
padding: 10px;
background: #232424;
width: max-content;
overflow: auto;
margin-top: 10px;
:deep(.upload-demo){
float:right; display: flex; flex-direction: column-reverse;
width:calc(100% - 53px);
position:relative;
.el-upload{
text-align: left;
}
.el-upload-list__item:hover{
background:#232424 !important ;
}
.el-upload-list__item:first-child{
margin-top:0px !important;
}
.el-upload-list__item-name [class^=el-icon]{
width: 40px;
height: 40px;
margin-bottom:10px;
font-size:40px;vertical-align: middle;
border-radius: 6px; line-height:40px;
border: 1px solid rgba(82, 84, 83, 0.6);
}
}
.parentbox{
height: calc(100% - 10px);
width: 300px;
float: left;
position: relative;
&:hover{
background:#1c1d1d;
}
border-radius: 6px;
padding:10px
}
.parent {
height: calc(100%);
width: 280px;
margin-right: 10px;
float: left;
position: relative;
.corridorCancel {
margin-top: 10px;
color: #cdcbc4;
font-size: 14px;
line-height: 30px;
float: right;
cursor: pointer;
margin-right: 15px;
}
.corridorAdd {
margin-top: 10px;
background: rgba(232, 69, 58, 0.5);
width: 60px;
line-height: 30px;
text-align: center;
height: 30px;
color: #fff;
cursor: pointer;
font-size: 14px;
float: right;
border-radius: 15px;
}
.addKanban {
border-radius: 20px;
width: 100%;
background: #313232;
border: 0px;
color: #fff;
font-size: 20px;
font-weight: bold;
margin-bottom: 10px;
}
.title {
color: #f0f0f0;
text-align: left;
height: 40px;
line-height: 40px;
font-size: 14px;
/deep/ .el-input {
width: calc(100% - 20px) !important;
}
// background:#575a59;
}
.drag {
width: 100%;
height: calc(100% - 50px);
overflow: auto;
}
/deep/ .el-input__inner {
border: 0px !important;
padding: 0px;
font-size: 16px;
color: #f0f0f0;
background: transparent !important;
}
/deep/ .el-input__inner:focus {
background-color: #313232 !important;
padding: 0px 10px;
border-radius: 10px !important;
}
/deep/ .el-textarea__inner {
background-color: #313232 !important;
padding: 10px;
border-radius: 10px !important;
border: 0px;
font-family: "element-icons";
color: #f0f0f0;
}
.focusinput {
/deep/ .el-input__inner {
background-color: #313232 !important;
padding: 0px 10px;
border-radius: 10px !important;
}
}
.textareabox {
&::after {
content: "";
display: block;
clear: both;
margin-bottom: 10px;
}
}
}
:deep() {
background: #232424;
.el-dialog .el-dialog__header {
padding: 0px 20px;
}
.el-dialog .el-dialog__body {
padding: 20px;
}
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.1);
border-radius: 6px;
box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.1);
}
}
.drag--from {
// background-color: #434544;
float: left;
margin-right: 20px;
}
.drag--to {
// background-color: #434544;
float: left;
}
.listpopper{
ul li{
margin-top:10px;color:#CDCBC4;cursor:pointer;
&:hover{
color:#E8453A
}
}
}
.box {
width: 100%;
padding: 10px;
background-color: #313232;
border-radius: 15px;
box-sizing: border-box;
margin-bottom: 10px;
box-shadow: 2px 2px 6px 0px rgba(0, 0, 0, 0.1);
color: #cdcbc4;
position:relative;
.boxDeal{
display: none;
position:absolute;
top:10px; right:10px;
width:20px; height:20px; border-radius: 50%; text-align: center; line-height: 20px; background: #434544;
}
&:hover{
.boxDeal{
display: inline-block;
}
}
.tagshow {
margin-top: 10px;
&::after {
content: "";
display: block;
clear: both;
}
span {
padding: 0px 15px;
font-size: 14px;
border-radius: 12px;
color: #fff;
margin-right: 10px;
margin-bottom: 5px;
height: 24px;
float: left;
line-height: 24px;
&.fengkong {
background: #32c845;
}
&.zhishu {
background: #bc1bfa;
}
&.ten {
background: #fa9d1b;
}
&.five {
background: #c83232;
}
}
}
.sub-titile {
color: coral;
font-size: 14px;
}
.content {
font-size: 14px;
margin-top: 10px;
}
}
.gu-mirror {
position: fixed !important;
margin: 0 !important;
z-index: 9999 !important;
opacity: 0.8;
transform: rotate(5deg);
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
filter: alpha(opacity=80);
}
.gu-hide {
display: none !important;
}
.gu-unselectable {
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
}
.gu-transit {
opacity: 0.2;
transform: rotate(5deg);
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
filter: alpha(opacity=20);
}
</style>