diff --git a/src/lib/common.js b/src/lib/common.js
index 94dd3396..c526677b 100644
--- a/src/lib/common.js
+++ b/src/lib/common.js
@@ -425,6 +425,48 @@ export function categorizedArrayToPlainArray(object) {
return ret;
}
+export function selectAll(filterItemIds, allItemsMap) {
+ for (let itemId in filterItemIds) {
+ if (!Object.prototype.hasOwnProperty.call(filterItemIds, itemId)) {
+ continue;
+ }
+
+ const item = allItemsMap[itemId];
+
+ if (item) {
+ filterItemIds[item.id] = false;
+ }
+ }
+}
+
+export function selectNone(filterItemIds, allItemsMap) {
+ for (let itemId in filterItemIds) {
+ if (!Object.prototype.hasOwnProperty.call(filterItemIds, itemId)) {
+ continue;
+ }
+
+ const item = allItemsMap[itemId];
+
+ if (item) {
+ filterItemIds[item.id] = true;
+ }
+ }
+}
+
+export function selectInvert(filterItemIds, allItemsMap) {
+ for (let itemId in filterItemIds) {
+ if (!Object.prototype.hasOwnProperty.call(filterItemIds, itemId)) {
+ continue;
+ }
+
+ const item = allItemsMap[itemId];
+
+ if (item) {
+ filterItemIds[item.id] = !filterItemIds[item.id];
+ }
+ }
+}
+
export function isPrimaryItemHasSecondaryValue(primaryItem, primarySubItemsField, secondaryValueField, secondaryValue) {
for (let i = 0; i < primaryItem[primarySubItemsField].length; i++) {
const secondaryItem = primaryItem[primarySubItemsField][i];
diff --git a/src/lib/services.js b/src/lib/services.js
index 4510eec0..a04a562a 100644
--- a/src/lib/services.js
+++ b/src/lib/services.js
@@ -282,15 +282,15 @@ export default {
id
});
},
- getTransactions: ({ maxTime, minTime, count, page, withCount, type, categoryIds, accountIds, amountFilter, keyword }) => {
+ getTransactions: ({ maxTime, minTime, count, page, withCount, type, categoryIds, accountIds, tagIds, amountFilter, keyword }) => {
amountFilter = encodeURIComponent(amountFilter);
keyword = encodeURIComponent(keyword);
- return axios.get(`v1/transactions/list.json?max_time=${maxTime}&min_time=${minTime}&type=${type}&category_ids=${categoryIds}&account_ids=${accountIds}&amount_filter=${amountFilter}&keyword=${keyword}&count=${count}&page=${page}&with_count=${withCount}&trim_account=true&trim_category=true&trim_tag=true`);
+ return axios.get(`v1/transactions/list.json?max_time=${maxTime}&min_time=${minTime}&type=${type}&category_ids=${categoryIds}&account_ids=${accountIds}&tag_ids=${tagIds}&amount_filter=${amountFilter}&keyword=${keyword}&count=${count}&page=${page}&with_count=${withCount}&trim_account=true&trim_category=true&trim_tag=true`);
},
- getAllTransactionsByMonth: ({ year, month, type, categoryIds, accountIds, amountFilter, keyword }) => {
+ getAllTransactionsByMonth: ({ year, month, type, categoryIds, accountIds, tagIds, amountFilter, keyword }) => {
amountFilter = encodeURIComponent(amountFilter);
keyword = encodeURIComponent(keyword);
- return axios.get(`v1/transactions/list/by_month.json?year=${year}&month=${month}&type=${type}&category_ids=${categoryIds}&account_ids=${accountIds}&amount_filter=${amountFilter}&keyword=${keyword}&trim_account=true&trim_category=true&trim_tag=true`);
+ return axios.get(`v1/transactions/list/by_month.json?year=${year}&month=${month}&type=${type}&category_ids=${categoryIds}&account_ids=${accountIds}&tag_ids=${tagIds}&amount_filter=${amountFilter}&keyword=${keyword}&trim_account=true&trim_category=true&trim_tag=true`);
},
getTransactionStatistics: ({ startTime, endTime, useTransactionTimezone }) => {
const queryParams = [];
diff --git a/src/lib/transaction.js b/src/lib/transaction.js
index 22a8761e..2ae494e2 100644
--- a/src/lib/transaction.js
+++ b/src/lib/transaction.js
@@ -22,7 +22,7 @@ function getDisplayAmount(amount, currency, hideAmount, formatAmountWithCurrency
return formatAmountWithCurrencyFunc(amount, currency);
}
-export function setTransactionModelByTransaction(transaction, transaction2, allCategories, allCategoriesMap, allVisibleAccounts, allAccountsMap, defaultAccountId, options, setContextData, convertContextTime) {
+export function setTransactionModelByTransaction(transaction, transaction2, allCategories, allCategoriesMap, allVisibleAccounts, allAccountsMap, allTagsMap, defaultAccountId, options, setContextData, convertContextTime) {
if ((!options.type || options.type === '0') && options.categoryId && options.categoryId !== '0' && allCategoriesMap[options.categoryId]) {
const category = allCategoriesMap[options.categoryId];
const type = categoryTypeToTransactionType(category.type);
@@ -105,6 +105,22 @@ export function setTransactionModelByTransaction(transaction, transaction2, allC
}
}
+ if (allTagsMap && options.tagIds) {
+ const tagIds = options.tagIds.split(',');
+ const finalTagIds = [];
+
+ for (let i = 0; i < tagIds.length; i++) {
+ const tagId = tagIds[i];
+ const tag = allTagsMap[tagId];
+
+ if (tag && !tag.hidden) {
+ finalTagIds.push(tag.id);
+ }
+ }
+
+ transaction.tagIds = finalTagIds;
+ }
+
if (transaction2) {
if (setContextData) {
transaction.id = transaction2.id;
diff --git a/src/locales/en.js b/src/locales/en.js
index 47ecddc9..bdc39f5f 100644
--- a/src/locales/en.js
+++ b/src/locales/en.js
@@ -1025,6 +1025,7 @@ export default {
'Multiple Accounts': 'Multiple Accounts',
'Source Account': 'Source Account',
'Destination Account': 'Destination Account',
+ 'Multiple Tags': 'Multiple Tags',
'Transaction Time': 'Transaction Time',
'Transaction Timezone': 'Transaction Timezone',
'Same time as default timezone': 'Same time as default timezone',
@@ -1098,6 +1099,7 @@ export default {
'Sort by Name': 'Sort by Name',
'Filter Accounts': 'Filter Accounts',
'Filter Transaction Categories': 'Filter Transaction Categories',
+ 'Filter Transaction Tags': 'Filter Transaction Tags',
'User Settings': 'User Settings',
'User Profile': 'User Profile',
'Language': 'Language',
diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js
index 4fe3efed..ad4b43f6 100644
--- a/src/locales/zh_Hans.js
+++ b/src/locales/zh_Hans.js
@@ -1025,6 +1025,7 @@ export default {
'Multiple Accounts': '多个账户',
'Source Account': '来源账户',
'Destination Account': '目标账户',
+ 'Multiple Tags': '多个标签',
'Transaction Time': '交易时间',
'Transaction Timezone': '交易时区',
'Same time as default timezone': '与默认时区时间相同',
@@ -1098,6 +1099,7 @@ export default {
'Sort by Name': '按名称排序',
'Filter Accounts': '过滤账户',
'Filter Transaction Categories': '过滤交易类型',
+ 'Filter Transaction Tags': '过滤交易标签',
'User Settings': '用户设置',
'User Profile': '用户信息',
'Language': '语言',
diff --git a/src/router/desktop.js b/src/router/desktop.js
index d0a2091b..6c571bd1 100644
--- a/src/router/desktop.js
+++ b/src/router/desktop.js
@@ -100,6 +100,7 @@ const router = createRouter({
initType: route.query.type,
initCategoryIds: route.query.categoryIds,
initAccountIds: route.query.accountIds,
+ initTagIds: route.query.tagIds,
initAmountFilter: route.query.amountFilter,
initKeyword: route.query.keyword
})
diff --git a/src/router/mobile.js b/src/router/mobile.js
index 385b7939..41687770 100644
--- a/src/router/mobile.js
+++ b/src/router/mobile.js
@@ -19,6 +19,7 @@ import TextSizeSettingsPage from '@/views/mobile/settings/TextSizeSettingsPage.v
import PageSettingsPage from '@/views/mobile/settings/PageSettingsPage.vue';
import AccountFilterSettingsPage from '@/views/mobile/settings/AccountFilterSettingsPage.vue';
import CategoryFilterSettingsPage from '@/views/mobile/settings/CategoryFilterSettingsPage.vue';
+import TransactionTagFilterSettingsPage from '@/views/mobile/settings/TransactionTagFilterSettingsPage.vue';
import SettingsPage from '@/views/mobile/SettingsPage.vue';
import ApplicationLockPage from '@/views/mobile/ApplicationLockPage.vue';
@@ -209,6 +210,11 @@ const routes = [
async: asyncResolve(CategoryFilterSettingsPage),
beforeEnter: [checkLogin]
},
+ {
+ path: '/settings/filter/tag',
+ async: asyncResolve(TransactionTagFilterSettingsPage),
+ beforeEnter: [checkLogin]
+ },
{
path: '/settings/page',
async: asyncResolve(PageSettingsPage),
diff --git a/src/stores/transaction.js b/src/stores/transaction.js
index 7dd2409b..2f9343eb 100644
--- a/src/stores/transaction.js
+++ b/src/stores/transaction.js
@@ -284,6 +284,7 @@ export const useTransactionsStore = defineStore('transactions', {
type: 0,
categoryIds: '',
accountIds: '',
+ tagIds: '',
amountFilter: '',
keyword: ''
},
@@ -324,6 +325,22 @@ export const useTransactionsStore = defineStore('transactions', {
return ret;
},
+ allFilterTagIds(state) {
+ if (!state.transactionsFilter.tagIds) {
+ return {};
+ }
+
+ const allTagIds = state.transactionsFilter.tagIds.split(',');
+ const ret = {};
+
+ for (let i = 0; i < allTagIds.length; i++) {
+ if (allTagIds[i]) {
+ ret[allTagIds[i]] = true;
+ }
+ }
+
+ return ret;
+ },
allFilterCategoryIdsCount(state) {
if (!state.transactionsFilter.categoryIds) {
return 0;
@@ -356,6 +373,22 @@ export const useTransactionsStore = defineStore('transactions', {
return count;
},
+ allFilterTagIdsCount(state) {
+ if (!state.transactionsFilter.tagIds) {
+ return 0;
+ }
+
+ const allTagIds = state.transactionsFilter.tagIds.split(',');
+ let count = 0;
+
+ for (let i = 0; i < allTagIds.length; i++) {
+ if (allTagIds[i]) {
+ count++;
+ }
+ }
+
+ return count;
+ },
noTransaction(state) {
for (let i = 0; i < state.transactions.length; i++) {
const transactionMonthList = state.transactions[i];
@@ -444,6 +477,7 @@ export const useTransactionsStore = defineStore('transactions', {
this.transactionsFilter.type = 0;
this.transactionsFilter.categoryIds = '';
this.transactionsFilter.accountIds = '';
+ this.transactionsFilter.tagIds = '';
this.transactionsFilter.amountFilter = '';
this.transactionsFilter.keyword = '';
this.transactions = [];
@@ -492,6 +526,12 @@ export const useTransactionsStore = defineStore('transactions', {
this.transactionsFilter.accountIds = '';
}
+ if (filter && isString(filter.tagIds)) {
+ this.transactionsFilter.tagIds = filter.tagIds;
+ } else {
+ this.transactionsFilter.tagIds = '';
+ }
+
if (filter && isString(filter.amountFilter)) {
this.transactionsFilter.amountFilter = filter.amountFilter;
} else {
@@ -537,6 +577,11 @@ export const useTransactionsStore = defineStore('transactions', {
changed = true;
}
+ if (filter && isString(filter.tagIds) && this.transactionsFilter.tagIds !== filter.tagIds) {
+ this.transactionsFilter.tagIds = filter.tagIds;
+ changed = true;
+ }
+
if (filter && isString(filter.amountFilter) && this.transactionsFilter.amountFilter !== filter.amountFilter) {
this.transactionsFilter.amountFilter = filter.amountFilter;
changed = true;
@@ -564,6 +609,10 @@ export const useTransactionsStore = defineStore('transactions', {
querys.push('categoryIds=' + this.transactionsFilter.categoryIds);
}
+ if (this.transactionsFilter.tagIds) {
+ querys.push('tagIds=' + this.transactionsFilter.tagIds);
+ }
+
querys.push('dateType=' + this.transactionsFilter.dateType);
if (this.transactionsFilter.dateType === datetimeConstants.allDateRanges.Custom.type) {
@@ -603,6 +652,7 @@ export const useTransactionsStore = defineStore('transactions', {
type: self.transactionsFilter.type,
categoryIds: self.transactionsFilter.categoryIds,
accountIds: self.transactionsFilter.accountIds,
+ tagIds: self.transactionsFilter.tagIds,
amountFilter: self.transactionsFilter.amountFilter,
keyword: self.transactionsFilter.keyword
}).then(response => {
@@ -678,6 +728,7 @@ export const useTransactionsStore = defineStore('transactions', {
type: self.transactionsFilter.type,
categoryIds: self.transactionsFilter.categoryIds,
accountIds: self.transactionsFilter.accountIds,
+ tagIds: self.transactionsFilter.tagIds,
amountFilter: self.transactionsFilter.amountFilter,
keyword: self.transactionsFilter.keyword
}).then(response => {
diff --git a/src/stores/transactionTag.js b/src/stores/transactionTag.js
index 3d2503c3..713b8d97 100644
--- a/src/stores/transactionTag.js
+++ b/src/stores/transactionTag.js
@@ -72,6 +72,9 @@ export const useTransactionTagsStore = defineStore('transactionTags', {
}
return allVisibleTags;
+ },
+ allVisibleTagsCount(state) {
+ return state.allTransactionTags.length;
}
},
actions: {
diff --git a/src/views/desktop/common/cards/TransactionTagFilterSettingsCard.vue b/src/views/desktop/common/cards/TransactionTagFilterSettingsCard.vue
new file mode 100644
index 00000000..95a21511
--- /dev/null
+++ b/src/views/desktop/common/cards/TransactionTagFilterSettingsCard.vue
@@ -0,0 +1,284 @@
+
+ {{ $t(title) }}
+