需求描述
在vue+arco的项目中做一个导出按钮前置弹窗,弹窗内含一个穿梭框,并且支持穿梭框内有默认选中字段并且可以自己调整字段左右及上下位置以便导出表格内字段位置显示
实现过程
因现在大部分ui库都有穿梭框 所以选择直接进行封装 为了方便 我把弹窗组件所有代码全部放入 按需取用
<template>
<a-modal
v-model:visible="exportVisible.visible"
title-align="start"
width="720px"
title="导出"
>
<template #footer>
<div style="text-align: center">
<a-space :size="18">
<a-button
type="primary"
:disabled="!exportVisible.expertId.length"
@click="derive(1)"
>
导出当前列表
</a-button>
<a-button type="primary" @click="derive(2)">导出全部</a-button>
<a-button type="outline" @click="derive(3)">取消</a-button>
</a-space>
</div>
</template>
<a-space fill :size="20">
<a-transfer
v-if="exportVisible.visible"
v-model="exportVisible.expertId"
v-model:selected="exportVisible.selectId"
show-search
:data="data"
:source-input-search-props="{ placeholder: '请输入可选字段' }"
:target-input-search-props="{ placeholder: '请输入已选导出字段' }"
>
<template #source-title> 可选字段 </template>
<template #target-title> 已选导出字段 </template>
</a-transfer>
<a-space direction="vertical">
<a-button
:disabled="!targetSelect"
shape="round"
@click="() => upBtn()"
>
<template #icon>
<icon-up />
</template>
</a-button>
<a-button
:disabled="!targetSelect"
shape="round"
@click="() => downBtn()"
>
<template #icon>
<icon-down />
</template>
</a-button>
</a-space>
</a-space>
<a-checkbox v-model="exportVisible.check" style="margin-top: 20px">
多专家会诊病例按专家拆分
</a-checkbox>
</a-modal>
</template>
<script setup lang="ts">
import { exportDynamic } from "@/api/diagnosis";
import { Message } from "@arco-design/web-vue";
import dayjs from "dayjs";
defineOptions({ name: "ExpertModal" });
const exportVisible = ref<any>({
visible: false,
allotStatus: 0,
check: false,
expertId: [],
selectId: [],
query: {},
});
const transfer = ref();
const data = ref([]);
const targetSelect = computed(() => {
return (
exportVisible.value.expertId.length &&
exportVisible.value.expertId.some((item) =>
exportVisible.value.selectId.includes(item),
)
);
});
const open = (params: any, cleck: any, query: any) => {
data.value = params.value
.map((item) => {
return {
value: item.dataIndex,
label: item.title,
};
})
.filter((item) => item.label !== "操作");
exportVisible.value.query = query;
if (cleck && cleck.length) {
exportVisible.value.expertId = cleck;
}
exportVisible.value.visible = true;
};
const expertIdChange = (value) => {
exportVisible.value.selectId = value;
};
// 向下移动
const downBtn = () => {
const expertId = exportVisible.value.expertId;
const intersect = expertId.filter((item) =>
exportVisible.value.selectId.includes(item),
);
intersect.forEach((item) => {
const tempIndex = expertId.findIndex((i) => i === item);
if (expertId[tempIndex + 1]) {
const nextItem = expertId[tempIndex + 1];
const dataTempIndex = data.value.findIndex((i) => i.value === item);
const dataNextTempIndex = data.value.findIndex(
(i) => i.value === nextItem,
);
// 右侧导入数组交换
const expertIdItemToMove = expertId.splice(tempIndex, 1)[0];
expertId.splice(tempIndex + 1, 0, expertIdItemToMove);
// 原数组交换
const itemToMove = data.value.splice(dataTempIndex, 1)[0];
data.value.splice(dataNextTempIndex, 0, itemToMove);
}
});
};
// 向上移动
const upBtn = () => {
const expertId = exportVisible.value.expertId;
const intersect = expertId.filter((item) =>
exportVisible.value.selectId.includes(item),
);
intersect.forEach((item) => {
const tempIndex = expertId.findIndex((i) => i === item);
if (tempIndex) {
const nextItem = expertId[tempIndex - 1];
const dataTempIndex = data.value.findIndex((i) => i.value === item);
const dataNextTempIndex = data.value.findIndex(
(i) => i.value === nextItem,
);
// 右侧导入数组交换
const expertIdItemToMove = expertId.splice(tempIndex, 1)[0];
expertId.splice(tempIndex - 1, 0, expertIdItemToMove);
// 原数组交换
const itemToMove = data.value.splice(dataTempIndex, 1)[0];
data.value.splice(dataNextTempIndex, 0, itemToMove);
}
});
};
const close = () => {
exportVisible.value = {
visible: false,
allotStatus: 0,
check: false,
expertId: [],
selectId: [],
query: {},
};
};
const derive = async (item) => {
let params;
if (item === 2||item === 1) {
params = {
consultationFieldInfos: exportVisible.value.expertId,
};
} else {
close();
return;
}
const res = await exportDynamic({
...params,
isSplitByExpert: exportVisible.value.check,
pageReqVO: item===1?{
...exportVisible.value.query,
}:{
siteStatus:exportVisible.value.query.siteStatus,
},
});
if (res) {
const elink = document.createElement("a");
elink.download = `明细${dayjs().format('YYYY-MM-DD HH:mm')}.xlsx`;
elink.style.display = "none";
const blob = new Blob([res], { type: "application/x-msdownload" });
elink.href = URL.createObjectURL(blob);
document.body.appendChild(elink);
elink.click();
document.body.removeChild(elink);
Message.success("导出成功");
close();
}
};
defineExpose({
open,
});
</script>
<style lang="less" scoped>
:deep(.cps-transfer) {
.cps-transfer-view {
width: 280px;
height: 500px;
}
}
</style>
外部打开弹窗及传参代码↓
<!--导出-->
<Export ref="exportRef" @done="search" />
const exportRef = ref<InstanceType<typeof Export>>();
// 打开导出
const exportData = () => {
//默认1
const DIAGNOSED = [
"consultationNo",
"pathologyType",
"caseNo",
"patientNickname",
"patientAge",
"sex",
"deliveryDept",
"applyDeptName",
"consultationExpertName",
"createTime",
"consultationIdea",
"consultationDeptName",
"diagnoseTime",
];
// 默认2
const ARCHIVED = [
"consultationNo",
"pathologyType",
"caseNo",
"patientNickname",
"patientAge",
"sex",
"deliveryDept",
"applyDeptName",
"consultationExpertName",
"createTime",
"consultationIdea",
"consultationDeptName",
"diagnoseTime",
"archivedTime",
];
const { time = [], ...rest } = query;
const [startTime, endTime] = time;
exportRef.value?.open(
columns,//所有字段
activeKey.value === TabEnum.DIAGNOSED ? DIAGNOSED : ARCHIVED,//打开弹窗默认已选字段
{//外部列表筛选条件 导出当前列表所需要
...rest,
siteStatus: unref(activeKey),
startTime: startTime ? `${startTime} 00:00:00` : undefined,
endTime: endTime ? `${endTime} 23:59:59` : undefined,
},
);
};
注1:打开弹窗默认已选字段的顺序必须和所有字段里面字段的顺序位置一致 否则会导致上下切换出现乱序问题 例:全部字段:【one,two,three】 默认展示字段【two,three】 不可【three,two】
注2:在弹窗组件内备注的右侧导入数组交换实际和页面无关 正在导致页面内字段位置交换的是原数组交换部分 穿梭框组件只会根据原数组位置变化去变化 而右侧导入数组位置交换部分是为了在导出的时候把选中字段的顺序传给后端