<template>
|
<div class="cl-select-table__picker">
|
<template v-if="pickerType == 'table'">
|
<template v-if="multiple">
|
<div class="mb-[10px]">
|
<el-button type="success" @click="open">{{ $t('添加') }}</el-button>
|
<el-button
|
type="danger"
|
:disabled="refs.table?.selection.length == 0"
|
@click="remove()"
|
>
|
{{ $t('移除') }}
|
</el-button>
|
</div>
|
|
<cl-crud padding="0">
|
<cl-row>
|
<cl-table :ref="setRefs('table')" :data="data" :auto-height="false" />
|
</cl-row>
|
|
<cl-row type="flex" align="middle" justify="end">
|
<el-pagination
|
v-model:current-page="pager.page"
|
:page-size="pager.size"
|
:total="list.length"
|
background
|
layout="total, prev, pager, next, jumper"
|
/>
|
</cl-row>
|
</cl-crud>
|
</template>
|
</template>
|
|
<template v-else>
|
<div class="cl-select-table__picker-inner" @click="open">
|
<span v-if="isEmpty(data)" class="placeholder">{{
|
placeholder || $t('请选择')
|
}}</span>
|
|
<template v-else>
|
<template v-for="(item, index) in data" :key="index">
|
<slot name="item" :item="item" :index="index">
|
<template v-if="pickerType == 'default'">
|
<cl-image
|
:src="item[dict.img]"
|
:size="24"
|
:radius="5"
|
:preview="false"
|
class="mr-[5px]"
|
/>
|
<span v-if="!multiple">{{ item[dict.text] }}</span>
|
</template>
|
|
<template v-else-if="pickerType == 'text'">
|
<el-tag
|
disable-transitions
|
size="small"
|
effect="plain"
|
v-if="multiple"
|
>
|
{{ item[dict.text] }}
|
</el-tag>
|
|
<span v-else>{{ item[dict.text] }}</span>
|
</template>
|
</slot>
|
</template>
|
</template>
|
|
<el-icon v-if="!isEmpty(data)" @click.stop="remove()">
|
<circle-close />
|
</el-icon>
|
</div>
|
</template>
|
</div>
|
|
<cl-dialog v-model="visible" width="1200px" :title="title">
|
<cl-crud ref="Crud" padding="0">
|
<cl-row>
|
<!-- 刷新按钮 -->
|
<cl-refresh-btn />
|
|
<!-- 全选 -->
|
<el-button v-if="multiple" type="primary" @click="selectAll">{{
|
$t('全选')
|
}}</el-button>
|
|
<cl-flex1 />
|
<!-- 条件搜索 -->
|
<cl-search ref="Search" />
|
</cl-row>
|
|
<cl-row>
|
<!-- 数据表格 -->
|
<cl-table ref="Table" :auto-height="false" @selection-change="onSelectionChange">
|
<template #column-check="{ scope }">
|
<el-button
|
v-if="selection[0]?.[dict.id] == scope.row[dict.id]"
|
type="success"
|
disabled
|
>
|
{{ $t('已选') }}
|
</el-button>
|
<el-button v-else @click="select(scope.row)">{{ $t('选择') }}</el-button>
|
</template>
|
</cl-table>
|
</cl-row>
|
|
<cl-row>
|
<span v-if="multiple">{{ $t('已选') }} {{ selection.length }} {{ $t('人') }}</span>
|
<cl-flex1 />
|
<!-- 分页控件 -->
|
<cl-pagination />
|
</cl-row>
|
</cl-crud>
|
|
<template #footer>
|
<el-button @click="close">{{ $t('取消') }}</el-button>
|
<el-button
|
v-if="multiple"
|
type="success"
|
:disabled="isEmpty(selection)"
|
@click="select()"
|
>
|
{{ $t('选择') }}
|
</el-button>
|
</template>
|
</cl-dialog>
|
</template>
|
|
<script lang="ts" setup>
|
defineOptions({
|
name: 'cl-select-table'
|
});
|
|
import { useI18n } from 'vue-i18n';
|
import { useCrud, useForm, useSearch, useTable } from '@cool-vue/crud';
|
import { useCool } from '/@/cool';
|
import { type PropType, computed, nextTick, reactive, ref, watch } from 'vue';
|
import { cloneDeep, isArray, isEmpty, merge } from 'lodash-es';
|
import { CircleClose } from '@element-plus/icons-vue';
|
import { CrudProps } from '../../comm';
|
|
const { t } = useI18n();
|
|
// 替换你的类型
|
type Item = {
|
id?: string | number;
|
nickName?: string;
|
avatarUrl?: string;
|
headImg?: string;
|
name?: string;
|
[key: string]: any;
|
};
|
|
// 字典
|
type Dict = {
|
id: string;
|
img: string;
|
text: string;
|
};
|
|
const props = defineProps({
|
...CrudProps,
|
modelValue: null,
|
scope: Object as PropType<Item>,
|
// 标题
|
title: String,
|
// 请选择
|
placeholder: String,
|
// 请求服务
|
service: null,
|
// 显示选择器
|
showPicker: {
|
type: Boolean,
|
default: true
|
},
|
// 选择器类型
|
pickerType: {
|
type: String as PropType<'default' | 'text' | 'table'>,
|
default: 'default'
|
},
|
// 字典
|
dict: Object as PropType<Dict>,
|
// 是否多选
|
multiple: {
|
type: Boolean,
|
default: true
|
},
|
// 列
|
columns: {
|
type: Array as PropType<ClTable.Column[]>,
|
default: () => []
|
}
|
});
|
|
const emit = defineEmits(['update:modelValue']);
|
|
const { refs, setRefs } = useCool();
|
|
// 字典
|
const dict = merge(
|
{
|
id: 'id',
|
img: 'avatarUrl',
|
text: 'nickName'
|
},
|
props.dict
|
);
|
|
// 上级表单
|
const Form = useForm();
|
|
// cl-table
|
const Table = useTable({
|
contextMenu: ['refresh', 'order-desc', 'order-asc'],
|
columns: [
|
props.multiple
|
? {
|
type: 'selection',
|
width: 60,
|
reserveSelection: true
|
}
|
: {
|
label: t('操作'),
|
prop: 'check',
|
width: 100
|
},
|
...props.columns
|
]
|
});
|
|
// cl-search
|
const Search = useSearch();
|
|
// cl-crud
|
const Crud = useCrud({
|
service: props.service,
|
async onRefresh(params, { next }) {
|
const res = await next(params);
|
|
// 添加已加载列表的 id
|
loadIds.value.push(...res.list.map(e => e[dict.id]));
|
|
// 数据反选
|
selection.value.forEach(e => {
|
const d = Table.value?.data.find(a => a[dict.id] == e[dict.id]);
|
|
if (d) {
|
Table.value?.toggleRowSelection(d, true);
|
}
|
});
|
}
|
});
|
|
// 刷新
|
async function refresh(params?: any) {
|
return Crud.value?.refresh(params);
|
}
|
|
// 弹窗是否可见
|
const visible = ref(false);
|
|
// 已选的数据列表,双向绑定用
|
const list = ref<Item[]>([]);
|
|
// 已选列表
|
const selection = ref<Item[]>([]);
|
|
// 分页
|
const pager = reactive({
|
page: 1,
|
size: 10
|
});
|
|
// 数据列表
|
const data = computed(() => {
|
if (props.pickerType == 'table') {
|
const { page, size } = pager;
|
return list.value.slice((page - 1) * size, page * size);
|
} else {
|
return list.value;
|
}
|
});
|
|
// 已加载列表的 id
|
const loadIds = ref<number[]>([]);
|
|
// 监听已选列表
|
function onSelectionChange(arr: Item[]) {
|
// 已加载的
|
const ids = Array.from(new Set(loadIds.value));
|
|
// 过滤掉已加载的,再加上已选的
|
selection.value = selection.value.filter(e => !ids.includes(e[dict.id]!)).concat(...arr);
|
}
|
|
// 打开选择弹窗
|
function open() {
|
visible.value = true;
|
|
// 清空数据
|
loadIds.value = [];
|
|
// 设置已选
|
selection.value = cloneDeep(list.value);
|
|
nextTick(() => {
|
refresh({
|
size: 10
|
});
|
});
|
}
|
|
// 关闭选择弹窗
|
function close() {
|
visible.value = false;
|
}
|
|
// 设置值
|
function set(data: Item[] | Item) {
|
list.value = cloneDeep(isArray(data) ? data : [data]);
|
}
|
|
// 选择
|
function select(item?: Item) {
|
// 单选不触发 onSelectionChange 手动设置
|
if (item) {
|
selection.value = [item];
|
}
|
|
list.value = cloneDeep(selection.value || []);
|
|
close();
|
}
|
|
// 全选
|
async function selectAll() {
|
// 全部数据
|
await Crud.value?.refresh({ page: 1, size: 10000 }).then(res => {
|
list.value = res.list;
|
});
|
|
close();
|
}
|
|
// 移除
|
function remove() {
|
if (props.pickerType == 'table') {
|
const ids = ((refs.table?.selection || []) as any[]).map(e => e[dict.id]);
|
|
list.value = list.value.filter(e => {
|
// 清空选择状态
|
refs.table?.toggleRowSelection(e, false);
|
|
// 移除已选的
|
return !ids.find(id => id == e[dict.id]);
|
});
|
} else {
|
list.value = [];
|
}
|
}
|
|
// 监听已选列表,返回 ids/id
|
watch(
|
list,
|
(arr = []) => {
|
const ids = arr.map(e => e[dict.id]);
|
|
if (props.multiple) {
|
emit('update:modelValue', ids);
|
} else {
|
emit('update:modelValue', ids[0]);
|
}
|
|
Form.value?.validateField(props.prop);
|
},
|
{
|
deep: true
|
}
|
);
|
|
defineExpose({
|
set,
|
remove,
|
select,
|
selectAll
|
});
|
</script>
|
|
<style lang="scss" scoped>
|
.cl-select-table__picker {
|
&-inner {
|
display: flex;
|
align-items: center;
|
box-shadow: 0 0 0 1px var(--el-border-color) inset;
|
border-radius: var(--el-border-radius-base);
|
padding: 0 11px;
|
padding-right: 40px;
|
min-height: 32px;
|
cursor: pointer;
|
position: relative;
|
background-color: var(--el-bg-color);
|
transition: var(--el-transition-duration);
|
|
.cl-avatar {
|
margin-right: 6px;
|
}
|
|
.el-tag {
|
margin: 2px 5px 2px 0;
|
}
|
|
.el-icon,
|
.placeholder {
|
color: var(--el-text-color-placeholder);
|
}
|
|
.el-icon {
|
position: absolute;
|
right: 10px;
|
}
|
|
.placeholder {
|
color: var(--el-text-color-placeholder);
|
}
|
|
&:hover {
|
box-shadow: 0 0 0 1px var(--el-text-color-disabled) inset;
|
}
|
}
|
}
|
</style>
|