注意 类名使用的是全局定义的类名 flex 为 display:flex, align-center为align-items:center, p-10为 padding: 10px; w-100为width:100%; f16 font-size:16px; 依次类推
<template>
<!-- 手动穿梭框 -->
<div class="flex align-center justify-between" :style="alterStyle" @click="data.setBtnKey = NaN">
<!-- 左边内容 -->
<div class="border radius-5 hidden" :style="leftRightStyle">
<!-- 头部选中 -->
<div
class="
w-100
border-bottom
p-10
flex
align-center
justify-between
box-sizing
"
style="height: 40px; background: #f5f7fa"
>
<div class="flex align-center">
<el-checkbox
v-model="data.leftSelectAll"
:disabled="data.whetherCan ? true : !data.leftData.length"
@change="leftAllCheckbx"
:indeterminate="
data.leftSelectAll ? false : Boolean(data.leftSelectLength)
"
>
</el-checkbox>
<p class="f16 ml-10">{{ titles[0] }}</p>
</div>
<p class="f12">
{{ data.leftSelectLength }}/{{ data.allDataList.length }}
</p>
</div>
<!-- 搜索框 -->
<div class="w-100 p-10">
<el-input
v-model="data.leftSearchText"
@input="leftSearch"
clearable
placeholder="请输入搜索内容"
>
</el-input>
</div>
<!-- 内容 -->
<div class="p-10 scrollEvent" style="overflow: auto; height: 300px">
<div class="hidden-scroll mb-10" v-show="data.leftData.length">
<div
v-for="item in data.leftData"
class="flex align-center"
:key="item[showKeyVal.key]"
>
<el-checkbox
v-model="item.checkbox"
:disabled="item.disabled"
@change="leftRadioSelect(item, 'notSet')"
:checked="item.checkbox"
></el-checkbox>
<p
class="ml-10 cp outText"
@click="leftRadioSelect(item)"
:style="item.checkbox === true ? 'color:#409eff' : ''"
>
<p class="labelText">{{ item[showKeyVal.label] }}</p>
</p>
</div>
</div>
<div
v-show="!data.leftData.length"
class="w-100 flex align-center justify-center"
>
<p>暂无数据</p>
</div>
</div>
</div>
<!-- 按钮 -->
<div class="flex algin-center flex-column">
<el-button
type="primary"
class="mb-10"
:disabled="Boolean(!data.leftSelectLength)"
@click="clickIncident('goto')"
>
{{ butText[1] }}
<el-icon><ArrowRight /></el-icon>
</el-button>
<el-button
type="primary"
style="margin: 0"
:disabled="Boolean(!data.rightSelectLength)"
@click="clickIncident('back')"
>
<el-icon><ArrowLeft /></el-icon>
{{ butText[0] }}
</el-button>
</div>
<!-- // 右侧内容 -->
<div class="border radius-5 hidden" :style="leftRightStyle">
<!-- // 头部选中 -->
<div
class="
w-100
border-bottom
p-10
flex
align-center
justify-between
box-sizing
"
style="height: 40px; background: #f5f7fa"
>
<div class="flex align-center">
<el-checkbox
v-model="data.rightSelectAll"
@change="rightAllCheckbx"
:disabled="data.rightSearchText? !data.rightSearchData.length : !data.rightData.length"
:indeterminate="
data.rightSelectAll ? false : Boolean(data.rightSelectLength)
"
>
</el-checkbox>
<p class="f16 ml-10">{{ titles[1] }}</p>
</div>
<p class="f12">
{{ data.rightSelectLength }}/{{ data.rightData.length }}
</p>
</div>
<!-- // 搜索框 -->
<div class="w-100 p-10">
<el-input
v-model="data.rightSearchText"
@input="rightSearch"
placeholder="请输入搜索内容"
clearable
>
</el-input>
</div>
<!-- // 内容 -->
<div class="p-10 scrollEvent" style="overflow: auto; height: 300px" @contextmenu="preventEvent($event)">
<div style="min-height: 200px" class="hidden-scroll" v-show="data.rightSearchText? data.rightSearchData.length: data.rightData.length">
<div
v-for="item in data.rightSearchText
? data.rightSearchData
: data.rightData"
class="flex align-center"
:key="item[showKeyVal.key]"
@mouseup="rightClick($event, item)"
>
<el-checkbox
v-model="item.checkbox"
@change="rightRadioSelect(item, 'notSet')"
:checked="item.checkbox"
></el-checkbox>
<p
class="ml-10 cp outText w-100"
:style="item.checkbox === true ? 'color:#409eff' : ''"
>
<p @click="rightRadioSelect(item)" class="labelText">{{ item[showKeyVal.label] }}</p>
<span v-show="data.labelShow === item[showKeyVal.key]" class="ml-10" style="color: red;">主单位</span>
<!-- // 按钮 -->
<p v-show="data.setBtnKey === item[showKeyVal.key]" @click="setHost" style="color: #3eacef">设为主单位</p>
</p>
</div>
</div>
<div
v-show="data.rightSearchText? !data.rightSearchData.length : !data.rightData.length"
class="w-100 flex align-center justify-center">
<p>暂无数据</p>
</div>
</div>
</div>
</div>
</template>
全局定义的css样式我就不贴了 太多了
<style lang="scss" scoped>
.hidden-scroll::-webkit-scrollbar {
display: none;
}
.outText {
display: flex;
overflow: hidden;
align-items: center;
text-overflow: ellipsis;
justify-content: space-between;
}
.labelText{
width: 130px;
height: 36px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
</style>
<script lang="ts">
import { reactive } from "@vue/reactivity";
import { onMounted, onUnmounted, watch } from "@vue/runtime-core";
import { useRouter } from "vue-router";
export default {
props: {
alterStyle: {
type: Object,
default: () => {
return {
width: "1000px",
};
},
}, // 主题样式
leftRightStyle: {
type: Object,
default: () => {
return {
width: "300px",
height: "400px",
};
},
},
// 主单位
primaryUnit: {
type: Number,
default: NaN,
},
titles: {
// 数据信息展示
type: Array,
default: () => {
return ["所有单位", "派发单位"];
},
},
butText: {
// 按钮文字显示
type: Array,
default: () => {
return ["左移", "右移"];
},
},
whetherPaging: {
// 默认打开分页 执行分页搜索查询
type: Boolean,
default: true,
},
selectDataUpdate: {
// 选中数据是否跟随数据源更新 (默认更新)
type: Boolean,
default: true,
},
allData: {
// 所有的数据
type: Array,
default: () => [],
},
showKeyVal: {
// 对应的键值对(需要展示的)
type: Object,
default: () => {
return {
key: "id",
label: "name",
};
},
},
selectData: {
// 被选中的数据
type: Array,
default: () => {
return []
},
},
astrictList: {
// 限制选中器
type: Array,
default: () => [],
},
showPageSize: {
// 每页显示多少
type: Number,
default: 1000,
},
},
setup(props, context) {
// 数据集合
let data = reactive({
labelShow: NaN, // 主单位key
setBtnKey: NaN, // 设置为主单位
allDataList: <any>[], // 所有数据的拷贝(用于更改状态)
showPageNum: 1, // 页数 (通过滚动添加)
// 左侧
leftData: <any>[], // 目前显示数据 // 左侧数据
leftSearchText: "", // 左侧内容搜索
leftSelectAll: false, // 左侧全选
whetherCan: false, // 是否可用
leftScrollEle: <any>"", // 滚动元素
leftSelectLength: <number>0, // 被选中元素有多少条
// 右侧
rightData: <any>[], // 右侧数据
rightDataCopy: <any>[], // 切换源也会保存数据 用于赋值
rightSearchData: [], // 右侧数据备份 用于搜索恢复
rightSearchText: "", // 右侧内容搜索
rightSelectAll: false, // 右侧全选
rigjtScorllEle: "", // 滚动元素
rightSelectLength: 0, // 被选中元素有多少条
});
onMounted(() => {
// 手动部分
// left滚动监听
if (props.whetherPaging) {
data.leftScrollEle = document.querySelectorAll(".scrollEvent")[0];
data.leftScrollEle.addEventListener("scroll", leftScroll, false);
}
});
// 组件被销毁前
onUnmounted(() => {
if (props.whetherPaging) {
data.leftScrollEle.removeEventListener("scroll", leftScroll);
}
});
// 重新排序 目前按照id大小排序
const sortFun = (obj: { [x: string]: number; }, obj1: { [x: string]: number; }) => {
if (obj[props.showKeyVal.key] > obj1[props.showKeyVal.key]) {
return 1;
} else {
return -1;
}
};
// 刷新
const refresh = () => {
data.leftSelectAll = false;
data.leftSelectLength = 0;
data.rightSelectAll = false
data.rightSelectLength = 0
// 进行深拷贝
if (Array.isArray(props.allData)) {
data.allDataList = JSON.parse(JSON.stringify(props.allData));
data.allDataList.sort(sortFun)
} else {
data.allDataList = [];
}
// 增加限制器 (不可被选中)
if (props.astrictList.length && data.allDataList.length) {
data.allDataList.forEach((item: any) => {
props.astrictList.forEach((ele: any) => {
if (item[props.showKeyVal.key] === ele[props.showKeyVal.key]) {
item.disabled = true;
}
});
});
}
// 左侧全选是否可选
let arr = data.allDataList.filter((item: any) => item.disabled);
if (data.allDataList.length === arr.length) {
data.whetherCan = true;
} else {
data.whetherCan = false;
}
// 回归初始位置 (数据切换时执行)
if (data.leftScrollEle) {
if (data.leftScrollEle.scrollTop !== 0) {
data.leftScrollEle.scrollTop = 0;
}
}
// 设置 主单位
if (props.primaryUnit) data.labelShow = props.primaryUnit
if (!props.selectDataUpdate) {
let arr = <any>[];
props.selectData.forEach((item: any) => {
if (data.rightDataCopy.indexOf(item[props.showKeyVal.key]) >= 0) {
arr.push(item);
}
});
bySelectData(arr);
} else {
bySelectData(props.selectData);
}
}
// 截取数组 实现分页
const pagingQuery = (allList: Array<object>, num: number, size: number) => {
if (!allList.length) {
return [];
} else {
let returnList = [];
returnList = allList.slice(0, size * num);
return returnList;
}
};
// 给leftData赋值(是否分页)
const setLeftData = () => {
// 是否分页
let arr = [];
if (props.whetherPaging) {
if (data.allDataList.length <= props.showPageSize * data.showPageNum) {
data.leftData = data.allDataList;
} else {
arr = [
...pagingQuery(
data.allDataList,
data.showPageNum,
props.showPageSize
),
];
// 去重
data.leftData = [
...new Set(arr.map((item) => JSON.stringify(item))),
].map((ele) => JSON.parse(ele));
}
} else {
data.leftData = data.allDataList;
}
};
// 被选中参数的方法 (赋值rightData)(父组件传递的值)
const bySelectData = (list: Array<any>) => {
if (list.length) {
if (!props.selectDataUpdate) {
let assignmentVal = <any>[];
list.forEach((item: any) => {
assignmentVal.push(
...data.allDataList.filter(
(ele: { [x: string]: any; }) => ele[props.showKeyVal.key] === item
)
);
});
let repetition = [...data.rightDataCopy, ...assignmentVal];
data.rightData = [
...new Set(repetition.map((item) => JSON.stringify(item))),
].map((ele) => JSON.parse(ele));
data.rightData.forEach((item: { checkbox: boolean; }) => {
item.checkbox = false;
});
data.rightSelectAll = data.rightData.every((item: { checkbox: any; }) => item.checkbox)
// 数据删除操作 (从左侧删除)
for (let i = 0; i < data.rightData.length; i++) {
for (let j = 0; j < data.allDataList.length; j++) {
if (
data.allDataList[j][props.showKeyVal.key] ==
data.rightData[i][props.showKeyVal.key]
) {
data.allDataList.splice(j, 1);
if (data.leftSelectLength >= 0) {
data.leftSelectLength--;
}
data.leftSelectAll = false;
}
}
}
} else {
let arr: any[] = [];
// 进行赋值操作
for (let i = 0; i < data.allDataList.length; i++) {
list.forEach((ele) => {
if (data.allDataList[i][props.showKeyVal.key] == ele) {
arr.push(data.allDataList[i]);
data.allDataList[i].checkbox = false;
}
});
}
data.rightData = [...data.rightData, ...arr];
data.rightSelectAll = data.rightData.every((item: { checkbox: any; }) => item.checkbox)
// 数据删除操作 (从左侧删除)
for (let i = 0; i < list.length; i++) {
for (let j = 0; j < data.allDataList.length; j++) {
if (data.allDataList[j][props.showKeyVal.key] == list[i]) {
data.allDataList.splice(j, 1);
if (data.leftSelectLength >= 0) {
data.leftSelectLength--;
}
data.leftSelectAll = false;
}
}
}
}
// 在左侧没有值时或者左侧值都不能选中时 禁用全选
if (data.allDataList.length) {
if (data.allDataList.length === data.allDataList.filter((item: {disabled: Boolean}) => item.disabled).length) {
data.whetherCan = true;
} else {
data.whetherCan = false;
}
} else {
data.whetherCan = true;
}
} else {
data.rightData = [];
// 不跟随切换值
if (!props.selectDataUpdate) {
data.rightData = [...data.rightDataCopy];
// 数据删除操作 (从左侧删除)
for (let i = 0; i < data.rightData.length; i++) {
for (let j = 0; j < data.allDataList.length; j++) {
if (
data.allDataList[j][props.showKeyVal.key] ===
data.rightData[i][props.showKeyVal.key]
) {
data.allDataList.splice(j, 1);
if (data.leftSelectLength >= 0) {
data.leftSelectLength--;
}
data.leftSelectAll = false;
}
}
}
}
// 在左侧没有值时或者左侧值都不能选中时 禁用全选
if (data.allDataList.length) {
if (
data.allDataList.length ===
data.allDataList.filter((item: {disabled: Boolean}) => item.disabled).length
) {
data.whetherCan = true;
} else {
data.whetherCan = false;
}
} else {
data.whetherCan = true;
}
}
// 无论data.rightData是否有值 给leftData赋值
setLeftData();
};
// 监听分页(全局)所有数据的监听 (切换数据源)
watch(
() => props.allData,
(val) => {
refresh()
},
{ immediate: true, deep: true }
);
// 左侧 (全局)
// 查询事件
const leftSearch = (text: any) => {
if (text) {
let arr: any[] = [];
data.allDataList.forEach((item: { [x: string]: string | any[]; }) => {
if (item[props.showKeyVal.label].indexOf(text) >= 0) {
arr.push(item);
}
});
data.leftData = JSON.parse(JSON.stringify(arr));
// 搜索项是否已经被全部选中
let list = data.leftData.filter((item: { disabled: any; }) => !item.disabled)
if (list.length) {
data.leftSelectAll = list.every((item: { checkbox: any; }) => item.checkbox)
} else {
data.leftSelectAll = false
}
} else {
let arr = data.allDataList.filter((item: { disabled: Boolean}) =>!item.disabled)
if (data.leftSelectLength !== arr.length) {
data.leftSelectAll = false
} else {
data.leftSelectAll = true
}
setLeftData();
}
};
// 单选事件
const leftRadioSelect = (item: { [x: string]: any; disabled: any; checkbox: boolean; }, notSet: string = '') => {
// 不可被选中项
if (item.disabled) return;
if (notSet === "notSet") {
if (item.checkbox) {
data.leftSelectLength++;
} else {
data.leftSelectLength--;
}
} else {
// 点击文本的选择
if (item.checkbox) {
data.leftSelectLength--;
} else {
data.leftSelectLength++;
}
item.checkbox = !item.checkbox;
}
data.allDataList.forEach((ele: {checkbox: Boolean}) => {
if (ele[props.showKeyVal.key] === item[props.showKeyVal.key]) {
ele.checkbox = item.checkbox;
}
});
// 区分搜索和非搜索情况
if (data.leftSearchText) {
let arr = data.leftData.filter((item: { disabled: any; }) => !item.disabled)
data.leftSelectAll = arr.every((item: { checkbox: any; }) => item.checkbox)
} else {
let arr = data.allDataList.filter((item: { disabled: Boolean }) => !item.disabled)
data.leftSelectAll = arr.every((item: {checkbox: Boolean}) => item.checkbox);
}
};
// 左侧内容全选事件
const leftAllCheckbx = (status: any) => {
// 全部选中
if (status) {
// 在搜索情况下
if (data.leftSearchText) {
data.leftData.forEach((item: { [x: string]: any; disabled: any; checkbox: boolean; }) => {
data.allDataList.forEach((ele: {checkbox: Boolean}) => {
if (item[props.showKeyVal.key] === ele[props.showKeyVal.key] && !item.disabled) {
item.checkbox = ele.checkbox = true
}
})
})
data.leftSelectLength = data.allDataList.filter((item: { checkbox: Boolean}) => item.checkbox).length
// 在非搜索情况下
} else {
data.leftData.forEach((item: { disabled: any; checkbox: boolean; }) => {
if (!item.disabled && !item.checkbox) {
item.checkbox = true
}
})
data.allDataList.forEach((ele: {disabled: Boolean, checkbox: Boolean}) => {
if (!ele.disabled && !ele.checkbox) {
ele.checkbox = true
}
})
data.leftSelectLength = data.allDataList.filter((item: {disabled: Boolean}) => !item.disabled).length
}
// 全部取消
} else {
if (data.leftSearchText) {
data.leftData.forEach((item: { [x: string]: any; checkbox: boolean; }) => {
data.allDataList.forEach((ele: {checkbox: Boolean}) => {
if (item[props.showKeyVal.key] === ele[props.showKeyVal.key]) {
item.checkbox = ele.checkbox = false
data.leftSelectLength--
}
})
})
// 非搜索取消全选
} else {
data.leftData.forEach((item: { disabled: any; checkbox: boolean; }) => {
if (!item.disabled) {
item.checkbox = false
}
})
data.allDataList.forEach((ele: {disabled: Boolean, checkbox: Boolean}) => {
if (!ele.disabled) {
ele.checkbox = false
}
})
data.leftSelectLength = 0
}
}
};
// 左侧滚动事件
const leftScroll = (e: { target: { scrollTop: number; }; }) => {
if (
e.target.scrollTop >=
props.showPageSize * data.showPageNum * 4 * 0.75
) {
data.showPageNum++;
setLeftData();
}
};
// 按钮事件
const clickIncident = (val: string) => {
data.leftSearchText = ''
data.rightSearchText = ''
data.rightSearchData = []
let arr: object[] = [];
// 退回到leftData 左移事件
if (val === "back") {
data.whetherCan = false;
// 先取出所有没有被选中的元素
let list = data.rightData.filter((item: { checkbox: any; }) => !item.checkbox);
// copy 剩余参数
if (!props.selectDataUpdate) data.rightDataCopy = list;
data.rightDataCopy.forEach((item: { checkbox: boolean; }) => (item.checkbox = false));
// 被选中元素量改为0
data.rightSelectLength = 0;
// 取消右侧全选
data.rightSelectAll = false;
// 遍历 把所有被选中的元素放入左边框
if (props.allData !== null && props.allData.length) {
data.rightData.forEach((item: { [x: string]: any; checkbox: boolean; }) => {
props.allData.forEach((ele: any) => {
if (item[props.showKeyVal.key] === ele[props.showKeyVal.key]) {
if (item.checkbox) {
// 主单位 被移除 同时
if (item[props.showKeyVal.key] === data.labelShow) {
data.labelShow = NaN
}
data.allDataList.push(item);
item.checkbox = false;
}
}
});
});
data.leftSelectAll = data.allDataList.every((item: {checkbox : Boolean}) => item.checkbox)
} else {
data.rightData.forEach((item: { checkbox: boolean; }) => {
item.checkbox = false;
});
}
// 给右边数组重新赋值
data.rightData = list;
// 如果右边还有元素
if (data.rightData.length) {
data.rightData.forEach((item: { [x: string]: any; }) => {
arr.push(item[props.showKeyVal.key]);
});
}
// 给左边元素重新排序
data.allDataList.sort(sortFun);
// 选中放入rightData
} else {
// 先把右侧的值弹出
if (data.rightData.length) {
data.rightData.forEach((ele: { [x: string]: any; }) => {
arr.push(ele[props.showKeyVal.key]);
});
}
// 再把左侧被选中的值弹出
data.allDataList.forEach((ele: { [x: string]: object; checkbox: Boolean; }) => {
if (ele.checkbox === true) {
arr.push(ele[props.showKeyVal.key]);
// 保存右侧数据 保留
if (!props.selectDataUpdate) {
data.rightDataCopy.push(ele);
}
}
});
data.rightDataCopy.forEach((item: {checkbox: Boolean}) => (item.checkbox = false));
// 被选中数据赋值
bySelectData(arr);
}
// 默认给第一个右侧数据添加主【单位】标签
// 如果有主单位则不调整主单位 否则选择为第一个
if (!data.labelShow) {
data.labelShow = data.rightData[0].id
}
context.emit('setHost', data.labelShow)
context.emit("transferChange", arr);
};
// 右侧
// 搜索事件
const rightSearch = (text: any) => {
if (text) {
let arr: any[] = [];
data.rightData.forEach((item: { [x: string]: string | any[]; }) => {
if (item[props.showKeyVal.label].indexOf(text) >= 0) {
arr.push(item);
}
});
data.rightSearchData = JSON.parse(JSON.stringify(arr));
// 搜索项被选中
if (data.rightSearchData.length) {
data.rightSelectAll = data.rightSearchData.every((item: {checkbox: Boolean}) => item.checkbox)
} else {
data.rightSelectAll = false
}
} else {
data.rightSearchData = [];
data.rightSelectAll = data.rightData.every((item: { checkbox: any; }) => item.checkbox)
}
};
// 阻止右侧右击默认事件
const preventEvent = (e: any) => {
e.preventDefault();
}
// 右侧右击事件
const rightClick = (e: any, val: {id: number}) => {
if (e.button == 2) {
if (data.labelShow === val.id) return
data.setBtnKey = val.id
}
}
// 设置主单位
const setHost = () => {
data.labelShow = data.setBtnKey
context.emit('setHost', data.labelShow)
}
// 右侧全选事件
const rightAllCheckbx = (status: any) => {
// 全选为true
if (status) {
// 搜索时
if (data.rightSearchText) {
data.rightSearchData.forEach((item: {checkbox: Boolean}) => {
data.rightData.forEach((ele: { [x: string]: any; checkbox: boolean; }) => {
if (item[props.showKeyVal.key] === ele[props.showKeyVal.key]) {
ele.checkbox = item.checkbox = true
}
})
})
data.rightSelectLength = data.rightData.filter((item: { checkbox: any; }) => item.checkbox).length
// 非搜索时
} else {
data.rightData.forEach((item: { checkbox: boolean; }) => {
item.checkbox = true
})
data.rightSelectLength = data.rightData.length
}
} else {
// 搜索时
if (data.rightSearchText) {
data.rightSearchData.forEach((item: {checkbox: Boolean}) => {
data.rightData.forEach((ele: { [x: string]: any; checkbox: boolean; }) => {
if (item[props.showKeyVal.key] === ele[props.showKeyVal.key]) {
item.checkbox = ele.checkbox = false
data.rightSelectLength--
}
})
})
} else {
data.rightData.forEach((item: { checkbox: boolean; }) => {
item.checkbox = false
})
data.rightSelectLength = 0
}
}
};
// 单选事件
const rightRadioSelect = (item: { [x: string]: any; checkbox: boolean; }, notSet: string = '') => {
// 无需更改值
if (notSet === "notSet") {
if (item.checkbox) {
data.rightSelectLength++;
} else {
data.rightSelectLength--;
}
} else {
// 点击文本
if (item.checkbox) {
data.rightSelectLength--;
} else {
data.rightSelectLength++;
}
item.checkbox = !item.checkbox;
}
if (data.rightSearchText) {
data.rightData.forEach((ele: { [x: string]: any; checkbox: any; }) => {
if (item[props.showKeyVal.key] === ele[props.showKeyVal.key]) {
ele.checkbox = item.checkbox
}
})
}
// 搜索单选 全选按钮的展示
if (data.rightSearchText) {
data.rightSelectAll = data.rightSearchData.every((item: {checkbox: Boolean}) => item.checkbox)
// 非搜索单选
} else {
data.rightData.rightSelectAll = data.rightData.every((item: { checkbox: any; }) => item.checkbox)
}
};
return {
data,
// left 部分
leftSearch,
leftScroll,
leftAllCheckbx,
leftRadioSelect,
// 按钮
clickIncident,
// right 部分
setHost,
rightSearch,
preventEvent,
rightClick,
rightAllCheckbx,
rightRadioSelect,
};
},
};
</script>
以上 实现的功能有 穿梭(基础功能) 右侧右击添加单位(业务需求)数据截取分页功能(毫无必要的需求,我都不知道我又没有写完🤷♂️)