support adding / renaming / deleting / changing display order for tag group on mobile version

This commit is contained in:
MaysWind
2026-01-17 20:04:07 +08:00
parent a357fb8136
commit 42ae323568
3 changed files with 302 additions and 2 deletions
+6
View File
@@ -44,6 +44,7 @@ import CategoryEditPage from '@/views/mobile/categories/EditPage.vue';
import CategoryPresetPage from '@/views/mobile/categories/PresetPage.vue';
import TagListPage from '@/views/mobile/tags/ListPage.vue';
import TagGroupListPage from '@/views/mobile/tags/GroupListPage.vue';
import TemplateListPage from '@/views/mobile/templates/ListPage.vue';
@@ -324,6 +325,11 @@ const routes: Router.RouteParameters[] = [
async: asyncResolve(TagListPage),
beforeEnter: [checkLogin]
},
{
path: '/tag/group/list',
async: asyncResolve(TagGroupListPage),
beforeEnter: [checkLogin]
},
{
path: '/template/list',
async: asyncResolve(TemplateListPage),
+193
View File
@@ -0,0 +1,193 @@
<template>
<f7-page @page:afterin="onPageAfterIn">
<f7-navbar>
<f7-nav-left :back-link="tt('Back')" v-if="!displayOrderModified"></f7-nav-left>
<f7-nav-left v-else-if="displayOrderModified">
<f7-link icon-f7="xmark" :class="{ 'disabled': displayOrderSaving }" @click="cancelSort"></f7-link>
</f7-nav-left>
<f7-nav-title :title="tt('Transaction Tag Groups')"></f7-nav-title>
<f7-nav-right class="navbar-compact-icons">
<f7-link icon-f7="checkmark_alt" :class="{ 'disabled': displayOrderSaving || !displayOrderModified }" @click="saveSortResult"></f7-link>
</f7-nav-right>
</f7-navbar>
<f7-list strong inset dividers class="tag-group-item-list margin-top skeleton-text" v-if="loading">
<f7-list-item :key="itemIdx" v-for="itemIdx in [ 1, 2, 3 ]">
<template #title>
<div class="display-flex">
<div class="transaction-tag-group-list-item-content list-item-valign-middle padding-inline-start-half">Tag Group Name</div>
</div>
</template>
</f7-list-item>
</f7-list>
<f7-list strong inset dividers class="tag-group-item-list margin-top" v-if="!loading && tagGroups.length < 1">
<f7-list-item :title="tt('No available tag group')"></f7-list-item>
</f7-list>
<f7-list strong inset dividers sortable sortable-enabled
class="tag-group-item-list margin-top"
@sortable:sort="onSort"
v-if="!loading">
<f7-list-item :id="getTagGroupDomId(tagGroup)"
:key="tagGroup.id"
v-for="tagGroup in tagGroups">
<template #title>
<div class="display-flex">
<div class="transaction-tag-group-list-item-content list-item-valign-middle padding-inline-start-half">{{ tagGroup.name }}</div>
</div>
</template>
</f7-list-item>
</f7-list>
</f7-page>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import type { Router } from 'framework7/types';
import { useI18n } from '@/locales/helpers.ts';
import { useI18nUIComponents, showLoading, hideLoading } from '@/lib/ui/mobile.ts';
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
import { TransactionTagGroup } from '@/models/transaction_tag_group.ts';
const props = defineProps<{
f7router: Router.Router;
}>();
const { tt } = useI18n();
const { showToast, routeBackOnError } = useI18nUIComponents();
const transactionTagsStore = useTransactionTagsStore();
const loading = ref<boolean>(true);
const loadingError = ref<unknown | null>(null);
const displayOrderModified = ref<boolean>(false);
const displayOrderSaving = ref<boolean>(false);
const tagGroups = computed<TransactionTagGroup[]>(() => transactionTagsStore.allTransactionTagGroups);
function getTagGroupDomId(tagGroup: TransactionTagGroup): string {
return 'tagGroup_' + tagGroup.id;
}
function parseTagGroupIdFromDomId(domId: string): string | null {
if (!domId || domId.indexOf('tagGroup_') !== 0) {
return null;
}
return domId.substring(9); // tagGroup_
}
function init(): void {
loading.value = true;
transactionTagsStore.loadAllTagGroups({
force: false
}).then(() => {
loading.value = false;
}).catch(error => {
if (error.processed) {
loading.value = false;
} else {
loadingError.value = error;
showToast(error.message || error);
}
});
}
function saveSortResult(): void {
if (!displayOrderModified.value) {
return;
}
displayOrderSaving.value = true;
showLoading();
transactionTagsStore.updateTagGroupDisplayOrders().then(() => {
displayOrderSaving.value = false;
hideLoading();
displayOrderModified.value = false;
}).catch(error => {
displayOrderSaving.value = false;
hideLoading();
if (!error.processed) {
showToast(error.message || error);
}
});
}
function cancelSort(): void {
if (!displayOrderModified.value) {
return;
}
displayOrderSaving.value = true;
showLoading();
transactionTagsStore.loadAllTagGroups({
force: false
}).then(() => {
displayOrderSaving.value = false;
hideLoading();
displayOrderModified.value = false;
}).catch(error => {
displayOrderSaving.value = false;
hideLoading();
if (!error.processed) {
showToast(error.message || error);
}
});
}
function onSort(event: { el: { id: string }, from: number, to: number }): void {
if (!event || !event.el || !event.el.id) {
showToast('Unable to move tag group');
return;
}
const id = parseTagGroupIdFromDomId(event.el.id);
if (!id) {
showToast('Unable to move tag group');
return;
}
transactionTagsStore.changeTagGroupDisplayOrder({
tagGroupId: id,
from: event.from,
to: event.to
}).then(() => {
displayOrderModified.value = true;
}).catch(error => {
showToast(error.message || error);
});
}
function onPageAfterIn(): void {
if (transactionTagsStore.transactionTagGroupListStateInvalid && !loading.value) {
transactionTagsStore.loadAllTagGroups({}).catch(error => {
if (!error.processed) {
showToast(error.message || error);
}
});
}
routeBackOnError(props.f7router, loadingError);
}
init();
</script>
<style>
.transaction-tag-group-list-item-content {
overflow: hidden;
text-overflow: ellipsis;
}
</style>
+103 -2
View File
@@ -12,7 +12,7 @@
</f7-link>
</f7-nav-title>
<f7-nav-right class="navbar-compact-icons">
<f7-link icon-f7="ellipsis" :class="{ 'disabled': hasEditingTag || !tags.length || sortable }" @click="showMoreActionSheet = true"></f7-link>
<f7-link icon-f7="ellipsis" :class="{ 'disabled': hasEditingTag || sortable }" @click="showMoreActionSheet = true"></f7-link>
<f7-link icon-f7="plus" :class="{ 'disabled': hasEditingTag }" v-if="!sortable" @click="add"></f7-link>
<f7-link icon-f7="checkmark_alt" :class="{ 'disabled': displayOrderSaving || !displayOrderModified || hasEditingTag }" @click="saveSortResult" v-else-if="sortable"></f7-link>
</f7-nav-right>
@@ -178,6 +178,17 @@
</f7-popup>
<f7-actions close-by-outside-click close-on-escape :opened="showMoreActionSheet" @actions:closed="showMoreActionSheet = false">
<f7-actions-group>
<f7-actions-button @click="addTagGroup">{{ tt('Add Tag Group') }}</f7-actions-button>
<f7-actions-button @click="renameTagGroup"
v-if="activeTagGroupId && activeTagGroupId !== DEFAULT_TAG_GROUP_ID">{{ tt('Rename Tag Group') }}</f7-actions-button>
<f7-actions-button color="red" :class="{ 'disabled': tags && tags.length > 0 }"
@click="removeTagGroup"
v-if="activeTagGroupId && activeTagGroupId !== DEFAULT_TAG_GROUP_ID">{{ tt('Delete Tag Group') }}</f7-actions-button>
</f7-actions-group>
<f7-actions-group>
<f7-actions-button @click="changeTagGroupDisplayOrder">{{ tt('Change Group Display Order') }}</f7-actions-button>
</f7-actions-group>
<f7-actions-group>
<f7-actions-button @click="setSortable()">{{ tt('Sort') }}</f7-actions-button>
<f7-actions-button v-if="!showHidden" @click="showHidden = true">{{ tt('Show Hidden Transaction Tags') }}</f7-actions-button>
@@ -211,7 +222,9 @@ import { useTagListPageBase } from '@/views/base/tags/TagListPageBase.ts';
import { useTransactionTagsStore } from '@/stores/transactionTag.ts';
import { TextDirection } from '@/core/text.ts';
import { DEFAULT_TAG_GROUP_ID } from '@/consts/tag.ts';
import { TransactionTagGroup } from '@/models/transaction_tag_group.ts';
import { TransactionTag } from '@/models/transaction_tag.ts';
import { scrollToSelectedItem } from '@/lib/ui/common.ts';
@@ -222,7 +235,7 @@ const props = defineProps<{
}>();
const { tt, getCurrentLanguageTextDirection } = useI18n();
const { showAlert, showToast, routeBackOnError } = useI18nUIComponents();
const { showAlert, showConfirm, showPrompt, showToast, routeBackOnError } = useI18nUIComponents();
const {
activeTagGroupId,
@@ -510,6 +523,94 @@ function cancelSort(): void {
});
}
function addTagGroup(): void {
showPrompt(tt('New Tag Group Name'), '', (value: string) => {
showLoading();
transactionTagsStore.saveTagGroup({
tagGroup: TransactionTagGroup.createNewTagGroup(value)
}).then(tagGroup => {
hideLoading();
activeTagGroupId.value = tagGroup.id;
}).catch(error => {
hideLoading();
if (!error.processed) {
showToast(error.message || error);
}
});
});
}
function renameTagGroup(): void {
const tagGroup = transactionTagsStore.allTransactionTagGroupsMap[activeTagGroupId.value];
if (!tagGroup) {
showToast('Unable to rename this tag group');
return;
}
showPrompt(tt('Rename Tag Group'), tagGroup.name || '', (value: string) => {
showLoading();
const newTagGroup = tagGroup.clone();
newTagGroup.name = value;
transactionTagsStore.saveTagGroup({
tagGroup: newTagGroup
}).then(() => {
hideLoading();
}).catch(error => {
hideLoading();
if (!error.processed) {
showToast(error.message || error);
}
});
});
}
function removeTagGroup(): void {
const tagGroup = transactionTagsStore.allTransactionTagGroupsMap[activeTagGroupId.value];
if (!tagGroup) {
showToast('Unable to delete this tag group');
return;
}
const currentTagGroupIndex = allTagGroupsWithDefault.value.findIndex(group => group.id === tagGroup.id);
showConfirm('Are you sure you want to delete this tag group?', () => {
showLoading();
transactionTagsStore.deleteTagGroup({
tagGroup: tagGroup
}).then(() => {
hideLoading();
if (allTagGroupsWithDefault.value[currentTagGroupIndex]) {
const newActiveTagGroup = allTagGroupsWithDefault.value[currentTagGroupIndex];
activeTagGroupId.value = newActiveTagGroup ? newActiveTagGroup.id : DEFAULT_TAG_GROUP_ID;
} else if (allTagGroupsWithDefault.value[currentTagGroupIndex - 1]) {
const newActiveTagGroup = allTagGroupsWithDefault.value[currentTagGroupIndex - 1];
activeTagGroupId.value = newActiveTagGroup ? newActiveTagGroup.id : DEFAULT_TAG_GROUP_ID;
} else {
activeTagGroupId.value = DEFAULT_TAG_GROUP_ID;
}
}).catch(error => {
hideLoading();
if (!error.processed) {
showToast(error.message || error);
}
});
});
}
function changeTagGroupDisplayOrder(): void {
props.f7router.navigate('/tag/group/list');
}
function onSort(event: { el: { id: string }, from: number, to: number }): void {
if (!event || !event.el || !event.el.id) {
showToast('Unable to move tag');