refactor: create select muliple
This commit is contained in:
parent
e19a15eaeb
commit
a5988bc3d4
2 changed files with 214 additions and 0 deletions
104
src/components/shared/select-muliple/SelectOffice.vue
Normal file
104
src/components/shared/select-muliple/SelectOffice.vue
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
|
||||||
|
import { createSelect, SelectProps } from './select-multiple';
|
||||||
|
import SelectInput from '../SelectInput.vue';
|
||||||
|
|
||||||
|
import useAddressStore, { Office } from 'stores/address';
|
||||||
|
|
||||||
|
type SelectOption = Office;
|
||||||
|
|
||||||
|
const value = defineModel<string[] | null | undefined>('value', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
const valueOption = defineModel<SelectOption[]>('valueOption', {
|
||||||
|
required: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectOptions = ref<SelectOption[]>([]);
|
||||||
|
|
||||||
|
const { fetchOffice: getList, fetchOfficeById: getById } = useAddressStore();
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
(e: 'create'): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
type ExclusiveProps = {};
|
||||||
|
|
||||||
|
const props = defineProps<SelectProps<typeof getList> & ExclusiveProps>();
|
||||||
|
|
||||||
|
const { getOptions, setFirstValue, getSelectedOption, filter } =
|
||||||
|
createSelect<SelectOption>(
|
||||||
|
{
|
||||||
|
value,
|
||||||
|
valueOption,
|
||||||
|
selectOptions,
|
||||||
|
getList: async (query) => {
|
||||||
|
const ret = await getList({
|
||||||
|
query,
|
||||||
|
...props.params,
|
||||||
|
});
|
||||||
|
if (ret) return ret;
|
||||||
|
},
|
||||||
|
getByValue: async (id) => {
|
||||||
|
const ret = await getById(id);
|
||||||
|
if (ret) return ret;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ valueField: 'id' },
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getOptions();
|
||||||
|
|
||||||
|
if (props.autoSelectOnSingle && selectOptions.value.length === 1) {
|
||||||
|
setFirstValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
await getSelectedOption();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<SelectInput
|
||||||
|
v-model="value"
|
||||||
|
option-value="id"
|
||||||
|
option-label="name"
|
||||||
|
incremental
|
||||||
|
multiple
|
||||||
|
:label
|
||||||
|
:placeholder
|
||||||
|
:readonly
|
||||||
|
:disable="disabled"
|
||||||
|
:option="selectOptions"
|
||||||
|
:hide-selected="false"
|
||||||
|
:fill-input="false"
|
||||||
|
:rules="[(v: string) => !!v || $t('form.error.required')]"
|
||||||
|
@filter="filter"
|
||||||
|
>
|
||||||
|
<template #before-options v-if="creatable">
|
||||||
|
<q-item clickable v-close-popup @click.stop="$emit('create')">
|
||||||
|
<q-item-section>
|
||||||
|
<b class="row items-center">
|
||||||
|
<q-icon
|
||||||
|
name="mdi-plus-circle-outline"
|
||||||
|
class="q-mr-sm"
|
||||||
|
style="color: hsl(var(--positive-bg))"
|
||||||
|
/>
|
||||||
|
{{ $t('general.add', { text: $t('quotation.newCustomer') }) }}
|
||||||
|
</b>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-separator class="q-mx-sm" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #append v-if="clearable">
|
||||||
|
<q-icon
|
||||||
|
v-if="!readonly && value"
|
||||||
|
name="mdi-close-circle"
|
||||||
|
@click.stop="value = []"
|
||||||
|
class="cursor-pointer clear-btn"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</SelectInput>
|
||||||
|
</template>
|
||||||
110
src/components/shared/select-muliple/select-multiple.ts
Normal file
110
src/components/shared/select-muliple/select-multiple.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { QSelect } from 'quasar';
|
||||||
|
import { ref, Ref, watch } from 'vue';
|
||||||
|
|
||||||
|
export type SelectProps<T extends (...args: any[]) => any> = {
|
||||||
|
params?: Parameters<T>[0];
|
||||||
|
creatable?: boolean;
|
||||||
|
label?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
readonly?: boolean;
|
||||||
|
required?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
clearable?: boolean;
|
||||||
|
autoSelectOnSingle?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createSelect = <T extends Record<string, any>>(
|
||||||
|
state: {
|
||||||
|
value: Ref<string[] | null | undefined>;
|
||||||
|
valueOption: Ref<T[] | undefined>;
|
||||||
|
selectOptions: Ref<T[]>;
|
||||||
|
getByValue: (id: string) => Promise<T | void> | T | void;
|
||||||
|
getList: (query?: string) => Promise<T[] | void> | T[] | void;
|
||||||
|
},
|
||||||
|
opts?: {
|
||||||
|
valueField?: keyof T;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const { value, valueOption, selectOptions, getList, getByValue } = state;
|
||||||
|
|
||||||
|
const valueField = opts?.valueField || 'value';
|
||||||
|
|
||||||
|
let cache: T[];
|
||||||
|
let previousSearch = '';
|
||||||
|
|
||||||
|
watch(value, (v) => {
|
||||||
|
console.log('UPDATED');
|
||||||
|
if (
|
||||||
|
!v ||
|
||||||
|
(cache && cache.find((opt) => v.find((val) => val === opt[valueField])))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getSelectedOption();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getOptions(query?: string) {
|
||||||
|
if (cache && selectOptions.value.length > 0 && previousSearch === query) {
|
||||||
|
selectOptions.value = JSON.parse(JSON.stringify(cache));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ret = await getList(query);
|
||||||
|
if (ret) {
|
||||||
|
cache = ret;
|
||||||
|
selectOptions.value = JSON.parse(JSON.stringify(cache));
|
||||||
|
previousSearch = query || previousSearch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setFirstValue() {
|
||||||
|
if (value.value) return;
|
||||||
|
const first = selectOptions.value.at(0);
|
||||||
|
if (first) value.value = first[valueField];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSelectedOption() {
|
||||||
|
const currentValue = value.value;
|
||||||
|
|
||||||
|
if (!currentValue) return;
|
||||||
|
if (selectOptions.value.find((v) => v[valueField] === currentValue)) return;
|
||||||
|
|
||||||
|
const newValueOptions = currentValue.map(async (a) => {
|
||||||
|
const findValue = valueOption.value?.find((b) => b[valueField] === a);
|
||||||
|
|
||||||
|
if (findValue) {
|
||||||
|
selectOptions.value.unshift(findValue);
|
||||||
|
return findValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ret = await getByValue(a);
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
selectOptions.value.unshift(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const retValueOptions = await Promise.all(newValueOptions);
|
||||||
|
|
||||||
|
valueOption.value = retValueOptions.flatMap((v) => (!!v ? v : []));
|
||||||
|
}
|
||||||
|
|
||||||
|
type QuasarSelectUpdate = (
|
||||||
|
callback: () => void,
|
||||||
|
afterFn?: ((ref: QSelect) => void) | undefined,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
function filter(value: string, update: QuasarSelectUpdate) {
|
||||||
|
update(
|
||||||
|
() => getOptions(value),
|
||||||
|
(ref) => {
|
||||||
|
if (!!value && ref.options && ref.options.length > 0) {
|
||||||
|
ref.setOptionIndex(-1);
|
||||||
|
ref.moveOptionSelection(1, true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { getOptions, setFirstValue, getSelectedOption, filter };
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue