左侧勾选对应右侧显示
右侧删除对应左侧取消勾选
效果图:
<template>
<el-dialog v-model="show" title="选择表头显示内容" draggable :close-on-click-modal="false"
@close="handleCloseDialog">
<div class="view">
<!-- 左边勾选区域 -->
<div class="view-left">
<div class="title">
可选属性(共{{items.length}}个)
</div>
<div class="left-content">
<div class="checkAll">
<el-checkbox v-model="allChecked" :indeterminate="isIndeterminate" @change="toggleAll">
全选
</el-checkbox>
</div>
<div class="left-content-select">
<div v-for="(item, index) in items" :key="index" class="item">
<label>
<input type="checkbox" v-model="item.checked" @change="toggleItem(item)" />
{{ item.label }}
</label>
</div>
</div>
</div>
</div>
<!-- <label>
<input type="checkbox" v-model="allChecked" @change="toggleAll"> 全选
</label> -->
<!-- 右边操作区域 -->
<div class="view-right">
<div class="title">
已选属性<span>(共{{selectedItems.length}}个)</span>
</div>
<div class="right-content">
<input type="text" v-model="searchText" placeholder="搜索..." class="search" />
<draggable v-model="selectedItems" item-key="id" @end="handleDrag"
:ghost-class="'dragging-ghost'"
:chosen-class="'dragging-chosen'"
:drag-class="'dragging-original'"
class="list">
<template #item="{ element }">
<div class="item-right">
<div v-show="element.label.includes(searchText)" class="item">
<div class="icon-left">
<el-icon><Grid /></el-icon>
<span>{{ element.label }}</span>
</div>
<div class="icon-right1">
<el-icon @click="topItem(element)" class="top">
<Top />
</el-icon>
<el-icon @click="deleteItem(element)" class="delete">
<Delete />
</el-icon>
</div>
<!-- <div>
<button @click="topItem(element)" class="btn">置顶</button>
<button @click="deleteItem(element)" class="btn">删除</button>
</div> -->
</div>
</div>
</template>
</draggable>
</div>
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCloseDialog">取消</el-button>
<el-button type="primary" @click="handleSubmitDialog">
确认
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { computed, ref, toRefs } from 'vue'
import draggable from 'vuedraggable'
import { formPropertySortSave } from '@/api/viewlist'
import { ElMessage } from 'element-plus'
const props = defineProps({
visible: {
type: Boolean,
default: true,
},
itemsArr: {
type: Array,
default: true,
}
})
// 创建本地副本
const items = ref(props.itemsArr)
// 监听props变化更新本地值
watch(() => props.itemsArr, (newVal) => {
items.value = newVal
console.log(items.value);
})
const selectedItems = ref([])
const searchText = ref('')
let show = toRefs(props).visible
const $emit = defineEmits(['closeShowDialog','reqGetlist','reqGetProjectViews'])
const handleCloseDialog = () => {
$emit('closeShowDialog')
}
const handleSubmitDialog = async () => {
// console.log(selectedItems.value);
const sortArr = selectedItems.value.map((item, index) => ({
// ...item,
formPropertyId:item.formPropertyId,
sort: index + 1
}));
const sortArr1 = JSON.stringify(
{
"formPropertyDtos": sortArr
}
)
// console.log(sortArr);
const res = await formPropertySortSave(sortArr1)
// console.log(res);
$emit('closeShowDialog')
$emit('reqGetlist')
$emit('reqGetProjectViews')
ElMessage.success('保存成功!')
}
// 全选计算属性
const allChecked = computed({
get: () =>
items.value.length > 0 && items.value.every((item) => item.checked),
set: (value) => toggleAll(value),
})
// 监听全选框按钮 立即监听 直接将左侧可选属性赋值给右侧已选属性
watch(() => allChecked.value, (newVal) => {
selectedItems.value = [...items.value]
},
{
immediate:true,
}
)
// 全选/全不选
const toggleAll = (checked) => {
items.value.forEach((item) => {
item.checked = checked
})
selectedItems.value = checked ? [...items.value] : []
}
// 单个选项切换
const toggleItem = (item) => {
if (item.checked) {
if (!selectedItems.value.some((i) => i.id === item.id)) {
selectedItems.value.push(item)
}
} else {
const index = selectedItems.value.findIndex((i) => i.id === item.id)
if (index > -1) selectedItems.value.splice(index, 1)
}
}
// 删除项
const deleteItem = (item) => {
const index = selectedItems.value.findIndex((i) => i.id === item.id)
if (index > -1) {
selectedItems.value.splice(index, 1)
items.value.find((i) => i.id === item.id).checked = false
}
}
// 置顶项
const topItem = (item) => {
const index = selectedItems.value.findIndex((i) => i.id === item.id)
if (index > -1) {
selectedItems.value.splice(index, 1)
selectedItems.value.unshift(item)
}
}
// 拖拽处理
const handleDrag = (val) => {
// v-model自动处理顺序,无需额外代码
}
</script>
<style lang="scss" scoped>
.container {
display: flex;
gap: 20px;
padding: 20px;
}
.left,
.right {
flex: 1;
border: 1px solid #ddd;
padding: 15px;
border-radius: 8px;
}
.btn {
margin-left: 8px;
padding: 4px 8px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn:hover {
background: #0056b3;
}
.view {
display: flex;
.title {
font-size: 16px;
font-weight: 600;
}
.view-left {
flex: 68%;
margin-right: 20px;
.left-content {
width: 100%;
// height: 100%;
height: 370px;
border: 1px solid #e0e0e0;
border-radius: 4px;
margin: 6px auto;
padding: 10px;
.checkAll {
padding-left: 6px;
}
.left-content-select {
display: flex;
flex-wrap: wrap;
.item {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
padding: 8px;
margin: 4px 0;
background: #ffffff;
border-radius: 4px;
input {
vertical-align: middle;
}
}
}
}
}
.view-right {
flex: 30%;
.right-content {
width: 100%;
border: 1px solid #e0e0e0;
border-radius: 4px;
margin: 6px auto;
padding: 10px;
.list{
height: 300px;
overflow: auto;
}
.search {
width: 100%;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
.item-right {
:hover{
background: #f0f0f0;
font-size: #fff;
}
// align-items: center;
.top {
// margin-left: 8px;
// cursor: pointer;
}
.icon-right {
// display: flex;
// flex-direction: flex-end;
}
.item {
display: flex;
align-items: center;
// margin: 6px auto;
padding: 8px;
cursor: pointer;
span{
font-size: 14px;
}
.icon-left {
flex: 7;
display: flex;
align-items: center;
:first-child {
// width: 18px;
// margin-right: 6px;
}
:nth-child(2) {
margin-left: 4px;
}
}
.icon-right1 {
flex: 2;
:first-child {
// width: 18px;
// margin-right: 6px;
}
:nth-child(2) {
margin-left: 8px;
}
}
}
}
}
}
}
/* 被拖动元素的原始位置样式 */
.dragging-original {
opacity: 0.5;
background: #1777ff !important;
}
/* 拖动时的占位符样式 */
.dragging-ghost {
background: #e3f2fd !important; /* 浅蓝色背景 */
// border: 2px dashed #2196f3 !important;
opacity: 0.8;
transform: rotate(3deg);
}
/* 被选中元素的样式 */
.dragging-chosen {
background: #1777ff !important; /* 浅橙色背景 */
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* HTML5拖拽后备样式 */
.dragging-fallback {
background: #ffebee !important;
}
</style>