migrate two column select to composition API and typescript

This commit is contained in:
MaysWind
2025-01-14 22:23:24 +08:00
parent d3e6756c22
commit cd2e6c1aae
4 changed files with 177 additions and 176 deletions
+177 -164
View File
@@ -20,8 +20,8 @@
<span class="text-truncate" v-if="!customSelectionPrimaryText && showSelectionPrimaryText && selectedPrimaryItem">{{ selectionPrimaryItemText }}</span> <span class="text-truncate" v-if="!customSelectionPrimaryText && showSelectionPrimaryText && selectedPrimaryItem">{{ selectionPrimaryItemText }}</span>
<v-icon class="disabled" :icon="icons.chevronRight" size="23" v-if="!customSelectionPrimaryText && showSelectionPrimaryText && selectedPrimaryItem && selectedSecondaryItem" /> <v-icon class="disabled" :icon="icons.chevronRight" size="23" v-if="!customSelectionPrimaryText && showSelectionPrimaryText && selectedPrimaryItem && selectedSecondaryItem" />
<ItemIcon class="mr-2" icon-type="account" size="21.5px" <ItemIcon class="mr-2" icon-type="account" size="21.5px"
:icon-id="selectedSecondaryItem ? selectedSecondaryItem[secondaryIconField] : null" :icon-id="selectedSecondaryItem && secondaryIconField ? (selectedSecondaryItem as Record<string, unknown>)[secondaryIconField] : null"
:color="selectedSecondaryItem ? selectedSecondaryItem[secondaryColorField] : null" :color="selectedSecondaryItem && secondaryColorField ? (selectedSecondaryItem as Record<string, unknown>)[secondaryColorField] : null"
v-if="!customSelectionPrimaryText && selectedSecondaryItem && showSelectionSecondaryIcon" /> v-if="!customSelectionPrimaryText && selectedSecondaryItem && showSelectionSecondaryIcon" />
<span class="text-truncate" v-if="!customSelectionPrimaryText && selectedSecondaryItem">{{ selectionSecondaryItemText }}</span> <span class="text-truncate" v-if="!customSelectionPrimaryText && selectedSecondaryItem">{{ selectionSecondaryItemText }}</span>
</div> </div>
@@ -32,38 +32,38 @@
<div class="primary-list-container"> <div class="primary-list-container">
<v-list :class="{ 'list-item-with-header': !!primaryHeaderField, 'list-item-with-footer': !!primaryFooterField }"> <v-list :class="{ 'list-item-with-header': !!primaryHeaderField, 'list-item-with-footer': !!primaryFooterField }">
<v-list-item :class="{ 'primary-list-item-selected v-list-item--active text-primary': item === selectedPrimaryItem }" <v-list-item :class="{ 'primary-list-item-selected v-list-item--active text-primary': item === selectedPrimaryItem }"
:key="primaryKeyField ? item[primaryKeyField] : item" :key="primaryKeyField ? (item as Record<string, unknown>)[primaryKeyField] : item"
v-for="item in items" v-for="item in items"
v-show="item && (!primaryHiddenField || !item[primaryHiddenField])" v-show="item && (!primaryHiddenField || !(item as Record<string, unknown>)[primaryHiddenField])"
@click="onPrimaryItemClicked(item)"> @click="onPrimaryItemClicked(item)">
<template #prepend> <template #prepend>
<ItemIcon class="mr-2" :icon-type="primaryIconType" <ItemIcon class="mr-2" :icon-type="primaryIconType"
:icon-id="item[primaryIconField]" :color="item[primaryColorField]"></ItemIcon> :icon-id="primaryIconField ? (item as Record<string, unknown>)[primaryIconField] : undefined" :color="primaryColorField ? (item as Record<string, unknown>)[primaryColorField] : undefined"></ItemIcon>
</template> </template>
<template #title> <template #title>
<div class="list-item-header text-truncate" v-if="primaryHeaderField">{{ $tIf(item[primaryHeaderField], primaryHeaderI18n) }}</div> <div class="list-item-header text-truncate" v-if="primaryHeaderField">{{ primaryHeaderField ? ti(item[primaryHeaderField] as string, !!primaryHeaderI18n) : '' }}</div>
<div class="text-truncate">{{ $tIf(item[primaryTitleField], primaryTitleI18n) }}</div> <div class="text-truncate">{{ primaryTitleField ? ti(item[primaryTitleField] as string, !!primaryTitleI18n) : '' }}</div>
<div class="list-item-footer text-truncate" v-if="primaryFooterField">{{ $tIf(item[primaryFooterField], primaryFooterI18n) }}</div> <div class="list-item-footer text-truncate" v-if="primaryFooterField">{{ primaryFooterField ? ti(item[primaryFooterField] as string, !!primaryFooterI18n) : '' }}</div>
</template> </template>
</v-list-item> </v-list-item>
</v-list> </v-list>
</div> </div>
<div class="secondary-list-container"> <div class="secondary-list-container">
<v-list :class="{ 'list-item-with-header': !!secondaryHeaderField, 'list-item-with-footer': !!secondaryFooterField }" <v-list :class="{ 'list-item-with-header': !!secondaryHeaderField, 'list-item-with-footer': !!secondaryFooterField }"
v-if="selectedPrimaryItem && primarySubItemsField && selectedPrimaryItem[primarySubItemsField]"> v-if="selectedPrimaryItem && primarySubItemsField && (selectedPrimaryItem as Record<string, unknown>)[primarySubItemsField]">
<v-list-item :class="{ 'secondary-list-item-selected v-list-item--active text-primary': isSecondarySelected(subItem) }" <v-list-item :class="{ 'secondary-list-item-selected v-list-item--active text-primary': isSecondarySelected(subItem) }"
:key="secondaryKeyField ? subItem[secondaryKeyField] : subItem" :key="secondaryKeyField ? subItem[secondaryKeyField] : subItem"
v-for="subItem in selectedPrimaryItem[primarySubItemsField]" v-for="subItem in (selectedPrimaryItem as Record<string, unknown>)[primarySubItemsField]"
v-show="subItem && (!secondaryHiddenField || !subItem[secondaryHiddenField])" v-show="subItem && (!secondaryHiddenField || !subItem[secondaryHiddenField])"
@click="onSecondaryItemClicked(subItem)"> @click="onSecondaryItemClicked(subItem)">
<template #prepend> <template #prepend>
<ItemIcon class="mr-2" :icon-type="secondaryIconType" <ItemIcon class="mr-2" :icon-type="secondaryIconType"
:icon-id="subItem[secondaryIconField]" :color="subItem[secondaryColorField]"></ItemIcon> :icon-id="secondaryIconField ? subItem[secondaryIconField] : undefined" :color="secondaryColorField ? subItem[secondaryColorField] : undefined"></ItemIcon>
</template> </template>
<template #title> <template #title>
<div class="list-item-header text-truncate" v-if="secondaryHeaderField">{{ $tIf(subItem[secondaryHeaderField], secondaryHeaderI18n) }}</div> <div class="list-item-header text-truncate" v-if="secondaryHeaderField">{{ secondaryHeaderField ? ti(subItem[secondaryHeaderField] as string, !!secondaryHeaderI18n) : '' }}</div>
<div class="text-truncate">{{ $tIf(subItem[secondaryTitleField], secondaryTitleI18n) }}</div> <div class="text-truncate">{{ ti(secondaryTitleField ? subItem[secondaryTitleField] as string : '', !!secondaryTitleI18n) }}</div>
<div class="list-item-footer text-truncate" v-if="secondaryFooterField">{{ $tIf(subItem[secondaryFooterField], secondaryFooterI18n) }}</div> <div class="list-item-footer text-truncate" v-if="secondaryFooterField">{{ secondaryFooterField ? ti(subItem[secondaryFooterField] as string, !!secondaryFooterI18n) : '' }}</div>
</template> </template>
</v-list-item> </v-list-item>
</v-list> </v-list>
@@ -73,7 +73,11 @@
</v-select> </v-select>
</template> </template>
<script> <script setup lang="ts">
import { ref, computed, useTemplateRef, nextTick } from 'vue';
import { useI18n } from '@/locales/helpers.ts';
import { import {
getFirstVisibleItem, getFirstVisibleItem,
getItemByKeyValue, getItemByKeyValue,
@@ -86,160 +90,169 @@ import {
mdiChevronRight mdiChevronRight
} from '@mdi/js'; } from '@mdi/js';
export default { const props = defineProps<{
props: [ modelValue: unknown;
'modelValue', density?: string;
'density', variant?: string;
'variant', disabled?: boolean;
'disabled', readonly?: boolean;
'readonly', label?: string;
'label', showSelectionPrimaryText?: boolean;
'showSelectionPrimaryText', showSelectionSecondaryIcon?: boolean;
'showSelectionSecondaryIcon', customSelectionPrimaryText?: string;
'customSelectionPrimaryText', customSelectionSecondaryText?: string;
'customSelectionSecondaryText', primaryKeyField?: string;
'primaryKeyField', primaryValueField?: string;
'primaryValueField', primaryTitleField?: string;
'primaryTitleField', primaryTitleI18n?: boolean;
'primaryTitleI18n', primaryHeaderField?: string;
'primaryHeaderField', primaryHeaderI18n?: boolean;
'primaryHeaderI18n', primaryFooterField?: string;
'primaryFooterField', primaryFooterI18n?: boolean;
'primaryFooterI18n', primaryIconField?: string;
'primaryIconField', primaryIconType?: string;
'primaryIconType', primaryColorField?: string;
'primaryColorField', primaryHiddenField?: string;
'primaryHiddenField', primarySubItemsField: string;
'primarySubItemsField', secondaryKeyField?: string;
'secondaryKeyField', secondaryValueField?: string;
'secondaryValueField', secondaryTitleField?: string;
'secondaryTitleField', secondaryTitleI18n?: boolean;
'secondaryTitleI18n', secondaryHeaderField?: string;
'secondaryHeaderField', secondaryHeaderI18n?: boolean;
'secondaryHeaderI18n', secondaryFooterField?: string;
'secondaryFooterField', secondaryFooterI18n?: boolean;
'secondaryFooterI18n', secondaryIconField?: string;
'secondaryIconField', secondaryIconType?: string;
'secondaryIconType', secondaryColorField?: string;
'secondaryColorField', secondaryHiddenField?: string;
'secondaryHiddenField', items: unknown[];
'noItemText', noItemText?: string;
'items' }>();
], const emit = defineEmits<{
emits: [ (e: 'update:modelValue', value: unknown): void;
'update:modelValue' }>();
],
data() {
return {
menuState: false,
icons: {
chevronRight: mdiChevronRight
}
}
},
computed: {
currentPrimaryValue: {
get: function () {
return this.getPrimaryValueBySecondaryValue(this.modelValue);
},
set: function (value) {
const primaryItem = getItemByKeyValue(this.items, value, this.primaryValueField);
const secondaryItem = getFirstVisibleItem(primaryItem[this.primarySubItemsField], this.primaryHiddenField);
if (secondaryItem) { const { tt, ti } = useI18n();
if (this.secondaryValueField) {
this.$emit('update:modelValue', secondaryItem[this.secondaryValueField]);
}
}
}
},
currentSecondaryValue: {
get: function () {
return this.modelValue;
},
set: function (value) {
this.menuState = false;
this.$emit('update:modelValue', value);
}
},
selectedPrimaryItem() {
if (this.primaryValueField) {
return getItemByKeyValue(this.items, this.currentPrimaryValue, this.primaryValueField);
} else {
return this.currentPrimaryValue;
}
},
selectedSecondaryItem() {
if (this.currentSecondaryValue && this.selectedPrimaryItem && this.selectedPrimaryItem[this.primarySubItemsField]) {
return getItemByKeyValue(this.selectedPrimaryItem[this.primarySubItemsField], this.currentSecondaryValue, this.secondaryValueField);
} else {
return null;
}
},
noSelectionText() {
return this.noItemText ? this.noItemText : this.$t('None');
},
selectionPrimaryItemText() {
if (this.primaryValueField && this.primaryTitleField) {
if (this.currentPrimaryValue) {
return getNameByKeyValue(this.items, this.currentPrimaryValue, this.primaryValueField, this.primaryTitleField, this.noSelectionText);
} else {
return this.noSelectionText;
}
} else {
return this.currentPrimaryValue;
}
},
selectionSecondaryItemText() {
if (this.secondaryValueField && this.secondaryTitleField) {
if (this.currentSecondaryValue && this.selectedPrimaryItem && this.selectedPrimaryItem[this.primarySubItemsField]) {
return getNameByKeyValue(this.selectedPrimaryItem[this.primarySubItemsField], this.currentSecondaryValue, this.secondaryValueField, this.secondaryTitleField, this.noSelectionText);
} else {
return this.noSelectionText;
}
} else {
return this.currentSecondaryValue;
}
}
},
methods: {
onPrimaryItemClicked(item) {
if (this.primaryValueField) {
this.currentPrimaryValue = item[this.primaryValueField];
} else {
this.currentPrimaryValue = item;
}
},
onSecondaryItemClicked(subItem) {
if (this.secondaryValueField) {
this.currentSecondaryValue = subItem[this.secondaryValueField];
} else {
this.currentSecondaryValue = subItem;
}
},
isSecondarySelected(subItem) {
if (this.secondaryValueField) {
return this.currentSecondaryValue === subItem[this.secondaryValueField];
} else {
return this.currentSecondaryValue === subItem;
}
},
getPrimaryValueBySecondaryValue(secondaryValue) {
return getPrimaryValueBySecondaryValue(this.items, this.primarySubItemsField, this.primaryValueField, this.primaryHiddenField, this.secondaryValueField, this.secondaryHiddenField, secondaryValue);
},
onMenuStateChanged(state) {
const self = this;
if (state) { const icons = {
self.$nextTick(() => { chevronRight: mdiChevronRight
if (self.$refs.dropdownMenu && self.$refs.dropdownMenu.parentElement) { };
scrollToSelectedItem(self.$refs.dropdownMenu.parentElement, '.primary-list-container', '.primary-list-item-selected');
scrollToSelectedItem(self.$refs.dropdownMenu.parentElement, '.secondary-list-container', '.secondary-list-item-selected'); const dropdownMenu = useTemplateRef<HTMLElement>('dropdownMenu');
}
}); const menuState = ref<boolean>(false);
const currentPrimaryValue = computed<unknown>({
get: () => {
return getCurrentPrimaryValueBySecondaryValue(props.modelValue);
},
set: (value) => {
const primaryItem = getItemByKeyValue(props.items as Record<string, unknown>[] | Record<string, Record<string, unknown>>, value, props.primaryValueField as string);
if (!primaryItem) {
return;
}
const secondaryItem = getFirstVisibleItem(primaryItem[props.primarySubItemsField] as Record<string, unknown>[] | Record<string, Record<string, unknown>>, props.primaryHiddenField as string);
if (secondaryItem) {
if (props.secondaryValueField) {
emit('update:modelValue', secondaryItem[props.secondaryValueField]);
} }
} }
} }
});
const currentSecondaryValue = computed<unknown>({
get: () => {
return props.modelValue;
},
set: (value) => {
menuState.value = false;
emit('update:modelValue', value);
}
});
const selectedPrimaryItem = computed<unknown>(() => {
if (props.primaryValueField) {
return getItemByKeyValue(props.items as Record<string, unknown>[] | Record<string, Record<string, unknown>>, currentPrimaryValue.value, props.primaryValueField);
} else {
return currentPrimaryValue.value;
}
});
const selectedSecondaryItem = computed<unknown>(() => {
if (currentSecondaryValue.value && selectedPrimaryItem.value && (selectedPrimaryItem.value as Record<string, unknown>)[props.primarySubItemsField]) {
return getItemByKeyValue((selectedPrimaryItem.value as Record<string, unknown>)[props.primarySubItemsField] as Record<string, unknown>[] | Record<string, Record<string, unknown>>, currentSecondaryValue.value, props.secondaryValueField as string);
} else {
return null;
}
});
const noSelectionText = computed<string>(() => props.noItemText ? props.noItemText : tt('None'));
const selectionPrimaryItemText = computed<string>(() => {
if (props.primaryValueField && props.primaryTitleField) {
if (currentPrimaryValue.value) {
return getNameByKeyValue(props.items as Record<string, unknown>[] | Record<string, Record<string, unknown>>, currentPrimaryValue.value, props.primaryValueField, props.primaryTitleField, noSelectionText.value) as string;
} else {
return noSelectionText.value;
}
} else {
return currentPrimaryValue.value as string;
}
});
const selectionSecondaryItemText = computed<string>(() => {
if (props.secondaryValueField && props.secondaryTitleField) {
if (currentSecondaryValue.value && selectedPrimaryItem.value && (selectedPrimaryItem.value as Record<string, unknown>)[props.primarySubItemsField]) {
return getNameByKeyValue((selectedPrimaryItem.value as Record<string, unknown>)[props.primarySubItemsField] as Record<string, unknown>[] | Record<string, Record<string, unknown>>, currentSecondaryValue.value, props.secondaryValueField, props.secondaryTitleField, noSelectionText.value) as string;
} else {
return noSelectionText.value;
}
} else {
return currentSecondaryValue.value as string;
}
});
function getCurrentPrimaryValueBySecondaryValue(secondaryValue: unknown): unknown {
return getPrimaryValueBySecondaryValue(props.items as Record<string, Record<string, unknown>[]>[] | Record<string, Record<string, Record<string, unknown>[]>>, props.primarySubItemsField, props.primaryValueField, props.primaryHiddenField, props.secondaryValueField, props.secondaryHiddenField, secondaryValue);
}
function isSecondarySelected(subItem: unknown): boolean {
if (props.secondaryValueField) {
return currentSecondaryValue.value === (subItem as Record<string, unknown>)[props.secondaryValueField];
} else {
return currentSecondaryValue.value === subItem;
}
}
function onPrimaryItemClicked(item: unknown): void {
if (props.primaryValueField) {
currentPrimaryValue.value = (item as Record<string, unknown>)[props.primaryValueField];
} else {
currentPrimaryValue.value = item;
}
}
function onSecondaryItemClicked(subItem: unknown): void {
if (props.secondaryValueField) {
currentSecondaryValue.value = (subItem as Record<string, unknown>)[props.secondaryValueField];
} else {
currentSecondaryValue.value = subItem;
}
}
function onMenuStateChanged(state: boolean): void {
if (state) {
nextTick(() => {
if (dropdownMenu.value && dropdownMenu.value.parentElement) {
scrollToSelectedItem(dropdownMenu.value.parentElement, '.primary-list-container', '.primary-list-item-selected');
scrollToSelectedItem(dropdownMenu.value.parentElement, '.secondary-list-container', '.secondary-list-item-selected');
}
});
}
} }
</script> </script>
-2
View File
@@ -73,7 +73,6 @@ import router from '@/router/desktop.js';
import { getVersion, getBuildTime } from '@/lib/version.ts'; import { getVersion, getBuildTime } from '@/lib/version.ts';
import { getI18nOptions } from '@/locales/helpers.ts'; import { getI18nOptions } from '@/locales/helpers.ts';
import { import {
translateIf,
translateError, translateError,
i18nFunctions i18nFunctions
} from '@/locales/helper.js'; } from '@/locales/helper.js';
@@ -473,7 +472,6 @@ app.config.globalProperties.$version = getVersion();
app.config.globalProperties.$buildTime = getBuildTime(); app.config.globalProperties.$buildTime = getBuildTime();
app.config.globalProperties.$locale = i18nFunctions(i18n.global); app.config.globalProperties.$locale = i18nFunctions(i18n.global);
app.config.globalProperties.$tIf = (text, isTranslate) => translateIf(text, isTranslate, i18n.global.t);
app.config.globalProperties.$tError = (message) => translateError(message, i18n.global.t); app.config.globalProperties.$tError = (message) => translateError(message, i18n.global.t);
app.mount('#app'); app.mount('#app');
-8
View File
@@ -1401,14 +1401,6 @@ function initLocale(i18nGlobal, lastUserLanguage, timezone) {
return localeDefaultSettings; return localeDefaultSettings;
} }
export function translateIf(text, isTranslate, translateFn) {
if (isTranslate) {
return translateFn(text);
}
return text;
}
export function translateError(message, translateFn) { export function translateError(message, translateFn) {
let parameters = {}; let parameters = {};
-2
View File
@@ -82,7 +82,6 @@ import '@vuepic/vue-datepicker/dist/main.css';
import { getVersion, getBuildTime } from '@/lib/version.ts'; import { getVersion, getBuildTime } from '@/lib/version.ts';
import { getI18nOptions } from '@/locales/helpers.ts'; import { getI18nOptions } from '@/locales/helpers.ts';
import { import {
translateIf,
i18nFunctions i18nFunctions
} from '@/locales/helper.js'; } from '@/locales/helper.js';
import { import {
@@ -206,7 +205,6 @@ app.config.globalProperties.$version = getVersion();
app.config.globalProperties.$buildTime = getBuildTime(); app.config.globalProperties.$buildTime = getBuildTime();
app.config.globalProperties.$locale = i18nFunctions(i18n.global); app.config.globalProperties.$locale = i18nFunctions(i18n.global);
app.config.globalProperties.$tIf = (text, isTranslate) => translateIf(text, isTranslate, i18n.global.t);
app.config.globalProperties.$alert = (message, confirmCallback) => showAlert(message, confirmCallback, i18n.global.t); app.config.globalProperties.$alert = (message, confirmCallback) => showAlert(message, confirmCallback, i18n.global.t);
app.config.globalProperties.$confirm = (message, confirmCallback, cancelCallback) => showConfirm(message, confirmCallback, cancelCallback, i18n.global.t); app.config.globalProperties.$confirm = (message, confirmCallback, cancelCallback) => showConfirm(message, confirmCallback, cancelCallback, i18n.global.t);