109 lines
2.8 KiB
Vue
109 lines
2.8 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import { Button as AButton, Select as ASelect } from 'ant-design-vue';
|
|
import { toFaDigits } from '@/lib/jalali';
|
|
|
|
type PickUser = { id: string; fullName: string };
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
modelValue: string[];
|
|
users: PickUser[];
|
|
disabled?: boolean;
|
|
/** حداقل تعداد انتخاب (حذف آخرین نفر غیرفعال میشود) */
|
|
min?: number;
|
|
addLabel?: string;
|
|
emptyLabel?: string;
|
|
/** دکمهٔ انتخاب یکجای همهٔ اعضا و جمعکردن برچسبها وقتی تعداد زیاد است */
|
|
selectAll?: boolean;
|
|
}>(),
|
|
{ disabled: false, min: 0, addLabel: 'افزودن', emptyLabel: 'کسی انتخاب نشده', selectAll: false },
|
|
);
|
|
|
|
const emit = defineEmits<{
|
|
'update:modelValue': [ids: string[]];
|
|
change: [ids: string[]];
|
|
}>();
|
|
|
|
const options = computed(() =>
|
|
props.users.map((u) => ({ value: u.id, label: u.fullName })),
|
|
);
|
|
|
|
const allPicked = computed(
|
|
() =>
|
|
props.users.length > 0 &&
|
|
props.users.every((user) => props.modelValue.includes(user.id)),
|
|
);
|
|
|
|
const tagLimit = computed(() => {
|
|
if (!props.selectAll) return undefined;
|
|
if (allPicked.value && props.users.length > 2) return 0;
|
|
return 'responsive' as const;
|
|
});
|
|
|
|
function tagPlaceholder(omitted: unknown[]) {
|
|
if (allPicked.value) return `همهٔ اعضا (${toFaDigits(props.users.length)})`;
|
|
const extra = Array.isArray(omitted) ? omitted.length : 0;
|
|
return extra ? `${toFaDigits(extra)} نفر دیگر` : '';
|
|
}
|
|
|
|
function commit(ids: string[]) {
|
|
if (ids.length < props.min && props.modelValue.length >= props.min) {
|
|
emit('update:modelValue', [...props.modelValue]);
|
|
return;
|
|
}
|
|
emit('update:modelValue', ids);
|
|
emit('change', ids);
|
|
}
|
|
|
|
function onUpdate(value: unknown) {
|
|
const ids = Array.isArray(value) ? value.map(String) : [];
|
|
commit(ids);
|
|
}
|
|
|
|
function pickAll() {
|
|
if (props.disabled || allPicked.value) return;
|
|
commit(props.users.map((user) => user.id));
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="user-multi" :class="{ 'with-all': selectAll }">
|
|
<a-select
|
|
mode="multiple"
|
|
allow-clear
|
|
show-search
|
|
option-filter-prop="label"
|
|
:value="modelValue"
|
|
:options="options"
|
|
:disabled="disabled"
|
|
:placeholder="emptyLabel"
|
|
:max-tag-count="tagLimit"
|
|
:max-tag-placeholder="selectAll ? tagPlaceholder : undefined"
|
|
style="width: 100%"
|
|
@update:value="onUpdate"
|
|
/>
|
|
<a-button v-if="selectAll" :disabled="disabled || allPicked || !users.length" @click="pickAll">
|
|
همه
|
|
</a-button>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.user-multi.with-all {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
width: 100%;
|
|
}
|
|
|
|
.user-multi.with-all :deep(.ant-select) {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
}
|
|
|
|
.user-multi.with-all :deep(.ant-btn) {
|
|
flex: 0 0 auto;
|
|
}
|
|
</style>
|