diff --git a/cmd/database.go b/cmd/database.go index d8ef2244..4b314d1d 100644 --- a/cmd/database.go +++ b/cmd/database.go @@ -91,5 +91,13 @@ func updateAllDatabaseTablesStructure() error { log.BootInfof("[database.updateAllDatabaseTablesStructure] transaction category table maintained successfully") } + err = datastore.Container.UserDataStore.SyncStructs(new(models.TransactionTag)) + + if err != nil { + return err + } else { + log.BootInfof("[database.updateAllDatabaseTablesStructure] transaction tag table maintained successfully") + } + return nil } diff --git a/cmd/webserver.go b/cmd/webserver.go index f9ead4ea..93aa8473 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -195,6 +195,15 @@ func startWebServer(c *cli.Context) error { apiV1Route.POST("/transaction/categories/move.json", bindApi(api.TransactionCategories.CategoryMoveHandler)) apiV1Route.POST("/transaction/categories/delete.json", bindApi(api.TransactionCategories.CategoryDeleteHandler)) + // Transaction Tags + apiV1Route.GET("/transaction/tags/list.json", bindApi(api.TransactionTags.TagListHandler)) + apiV1Route.GET("/transaction/tags/get.json", bindApi(api.TransactionTags.TagGetHandler)) + apiV1Route.POST("/transaction/tags/add.json", bindApi(api.TransactionTags.TagCreateHandler)) + apiV1Route.POST("/transaction/tags/modify.json", bindApi(api.TransactionTags.TagModifyHandler)) + apiV1Route.POST("/transaction/tags/hide.json", bindApi(api.TransactionTags.TagHideHandler)) + apiV1Route.POST("/transaction/tags/move.json", bindApi(api.TransactionTags.TagMoveHandler)) + apiV1Route.POST("/transaction/tags/delete.json", bindApi(api.TransactionTags.TagDeleteHandler)) + // Exchange Rates apiV1Route.GET("/exchange_rates/latest.json", bindApi(api.ExchangeRates.LatestExchangeRateHandler)) } diff --git a/pkg/api/transaction_tags.go b/pkg/api/transaction_tags.go new file mode 100644 index 00000000..09ef450b --- /dev/null +++ b/pkg/api/transaction_tags.go @@ -0,0 +1,219 @@ +package api + +import ( + "github.com/mayswind/lab/pkg/core" + "github.com/mayswind/lab/pkg/errs" + "github.com/mayswind/lab/pkg/log" + "github.com/mayswind/lab/pkg/models" + "github.com/mayswind/lab/pkg/services" +) + +type TransactionTagsApi struct { + tags *services.TransactionTagService +} + +var ( + TransactionTags = &TransactionTagsApi{ + tags: services.TransactionTags, + } +) + +func (a *TransactionTagsApi) TagListHandler(c *core.Context) (interface{}, *errs.Error) { + uid := c.GetCurrentUid() + tags, err := a.tags.GetAllTagsByUid(uid) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_tags.TagListHandler] failed to get tags for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrOperationFailed + } + + tagResps := make([]*models.TransactionTagInfoResponse, len(tags)) + + for i := 0; i < len(tags); i++ { + tagResps[i] = tags[i].ToTransactionTagInfoResponse() + } + + return tagResps, nil +} + +func (a *TransactionTagsApi) TagGetHandler(c *core.Context) (interface{}, *errs.Error) { + var tagGetReq models.TransactionTagGetRequest + err := c.ShouldBindQuery(&tagGetReq) + + if err != nil { + log.WarnfWithRequestId(c, "[transaction_tags.TagGetHandler] parse request failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + + uid := c.GetCurrentUid() + tag, err := a.tags.GetTagByTagId(uid, tagGetReq.Id) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_tags.TagGetHandler] failed to get tag \"id:%d\" for user \"uid:%d\", because %s", tagGetReq.Id, uid, err.Error()) + return nil, errs.ErrOperationFailed + } + + tagResp := tag.ToTransactionTagInfoResponse() + + return tagResp, nil +} + +func (a *TransactionTagsApi) TagCreateHandler(c *core.Context) (interface{}, *errs.Error) { + var tagCreateReq models.TransactionTagCreateRequest + err := c.ShouldBindJSON(&tagCreateReq) + + if err != nil { + log.WarnfWithRequestId(c, "[transaction_tags.TagCreateHandler] parse request failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + + uid := c.GetCurrentUid() + + maxOrderId, err := a.tags.GetMaxDisplayOrder(uid) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_tags.TagCreateHandler] failed to get max display order for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.ErrOperationFailed + } + + tag := a.createNewTagModel(uid, &tagCreateReq, maxOrderId+1) + + err = a.tags.CreateTag(tag) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_tags.TagCreateHandler] failed to create tag \"id:%d\" for user \"uid:%d\", because %s", tag.TagId, uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + log.InfofWithRequestId(c, "[transaction_tags.TagCreateHandler] user \"uid:%d\" has created a new tag \"id:%d\" successfully", uid, tag.TagId) + + tagResp := tag.ToTransactionTagInfoResponse() + + return tagResp, nil +} + +func (a *TransactionTagsApi) TagModifyHandler(c *core.Context) (interface{}, *errs.Error) { + var tagModifyReq models.TransactionTagModifyRequest + err := c.ShouldBindJSON(&tagModifyReq) + + if err != nil { + log.WarnfWithRequestId(c, "[transaction_tags.TagModifyHandler] parse request failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + + uid := c.GetCurrentUid() + tag, err := a.tags.GetTagByTagId(uid, tagModifyReq.Id) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_tags.TagModifyHandler] failed to get tag \"id:%d\" for user \"uid:%d\", because %s", tagModifyReq.Id, uid, err.Error()) + return nil, errs.ErrOperationFailed + } + + newTag := &models.TransactionTag{ + TagId: tag.TagId, + Uid: uid, + Name: tagModifyReq.Name, + } + + if newTag.Name == tag.Name { + return nil, errs.ErrNothingWillBeUpdated + } + + err = a.tags.ModifyTag(newTag) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_tags.TagModifyHandler] failed to update tag \"id:%d\" for user \"uid:%d\", because %s", tagModifyReq.Id, uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + log.InfofWithRequestId(c, "[transaction_tags.TagModifyHandler] user \"uid:%d\" has updated tag \"id:%d\" successfully", uid, tagModifyReq.Id) + + tag.Name = newTag.Name + tagResp := tag.ToTransactionTagInfoResponse() + + return tagResp, nil +} + +func (a *TransactionTagsApi) TagHideHandler(c *core.Context) (interface{}, *errs.Error) { + var tagHideReq models.TransactionTagHideRequest + err := c.ShouldBindJSON(&tagHideReq) + + if err != nil { + log.WarnfWithRequestId(c, "[transaction_tags.CategoryHideHandler] parse request failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + + uid := c.GetCurrentUid() + err = a.tags.HideTag(uid, []int64{tagHideReq.Id}, tagHideReq.Hidden) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_tags.CategoryHideHandler] failed to hide tag \"id:%d\" for user \"uid:%d\", because %s", tagHideReq.Id, uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + log.InfofWithRequestId(c, "[transaction_tags.CategoryHideHandler] user \"uid:%d\" has hidden category \"id:%d\"", uid, tagHideReq.Id) + return true, nil +} + +func (a *TransactionTagsApi) TagMoveHandler(c *core.Context) (interface{}, *errs.Error) { + var tagMoveReq models.TransactionTagMoveRequest + err := c.ShouldBindJSON(&tagMoveReq) + + if err != nil { + log.WarnfWithRequestId(c, "[transaction_tags.CategoryMoveHandler] parse request failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + + uid := c.GetCurrentUid() + tags := make([]*models.TransactionTag, len(tagMoveReq.NewDisplayOrders)) + + for i := 0; i < len(tagMoveReq.NewDisplayOrders); i++ { + newDisplayOrder := tagMoveReq.NewDisplayOrders[i] + tag := &models.TransactionTag{ + Uid: uid, + TagId: newDisplayOrder.Id, + DisplayOrder: newDisplayOrder.DisplayOrder, + } + + tags[i] = tag + } + + err = a.tags.ModifyTagDisplayOrders(uid, tags) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_tags.CategoryMoveHandler] failed to move tags for user \"uid:%d\", because %s", uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + log.InfofWithRequestId(c, "[transaction_tags.CategoryMoveHandler] user \"uid:%d\" has moved categories", uid) + return true, nil +} + +func (a *TransactionTagsApi) TagDeleteHandler(c *core.Context) (interface{}, *errs.Error) { + var tagDeleteReq models.TransactionTagDeleteRequest + err := c.ShouldBindJSON(&tagDeleteReq) + + if err != nil { + log.WarnfWithRequestId(c, "[transaction_tags.TagDeleteHandler] parse request failed, because %s", err.Error()) + return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) + } + + uid := c.GetCurrentUid() + err = a.tags.DeleteTags(uid, []int64{tagDeleteReq.Id}) + + if err != nil { + log.ErrorfWithRequestId(c, "[transaction_tags.TagDeleteHandler] failed to delete tag \"id:%d\" for user \"uid:%d\", because %s", tagDeleteReq.Id, uid, err.Error()) + return nil, errs.Or(err, errs.ErrOperationFailed) + } + + log.InfofWithRequestId(c, "[transaction_tags.TagDeleteHandler] user \"uid:%d\" has deleted tag \"id:%d\"", uid, tagDeleteReq.Id) + return true, nil +} + +func (a *TransactionTagsApi) createNewTagModel(uid int64, tagCreateReq *models.TransactionTagCreateRequest, order int) *models.TransactionTag { + return &models.TransactionTag{ + Uid: uid, + Name: tagCreateReq.Name, + DisplayOrder: order, + } +} diff --git a/pkg/errs/transaction_tag.go b/pkg/errs/transaction_tag.go new file mode 100644 index 00000000..e7d098ea --- /dev/null +++ b/pkg/errs/transaction_tag.go @@ -0,0 +1,10 @@ +package errs + +import "net/http" + +var ( + ErrTransactionTagIdInvalid = NewNormalError(NORMAL_SUBCATEGORY_TAG, 0, http.StatusBadRequest, "transaction tag id is invalid") + ErrTransactionTagNotFound = NewNormalError(NORMAL_SUBCATEGORY_TAG, 1, http.StatusBadRequest, "transaction tag not found") + ErrTransactionTagNameIsEmpty = NewNormalError(NORMAL_SUBCATEGORY_TAG, 2, http.StatusBadRequest, "transaction tag name is empty") + ErrTransactionTagNameAlreadyExists = NewNormalError(NORMAL_SUBCATEGORY_TAG, 3, http.StatusBadRequest, "transaction tag name already exists") +) diff --git a/pkg/models/transaction_tag.go b/pkg/models/transaction_tag.go new file mode 100644 index 00000000..1d6ed2f1 --- /dev/null +++ b/pkg/models/transaction_tag.go @@ -0,0 +1,72 @@ +package models + +type TransactionTag struct { + TagId int64 `xorm:"PK"` + Uid int64 `xorm:"UNIQUE(IDX_tag_uid_name) NOT NULL"` + Name string `xorm:"UNIQUE(IDX_tag_uid_name) VARCHAR(32) NOT NULL"` + DisplayOrder int `xorm:"NOT NULL"` + Hidden bool `xorm:"NOT NULL"` + CreatedUnixTime int64 + UpdatedUnixTime int64 +} + +type TransactionTagCreateRequest struct { + Name string `json:"name" binding:"required,notBlank,max=32"` +} + +type TransactionTagGetRequest struct { + Id int64 `form:"id,string" binding:"required,min=1"` +} + +type TransactionTagModifyRequest struct { + Id int64 `json:"id,string" binding:"required,min=1"` + Name string `json:"name" binding:"required,notBlank,max=32"` +} + +type TransactionTagHideRequest struct { + Id int64 `json:"id,string" binding:"required,min=1"` + Hidden bool `json:"hidden"` +} + +type TransactionTagMoveRequest struct { + NewDisplayOrders []*TransactionTagNewDisplayOrderRequest `json:"newDisplayOrders"` +} + +type TransactionTagNewDisplayOrderRequest struct { + Id int64 `json:"id,string" binding:"required,min=1"` + DisplayOrder int `json:"displayOrder"` +} + +type TransactionTagDeleteRequest struct { + Id int64 `json:"id,string" binding:"required,min=1"` +} + +type TransactionTagInfoResponse struct { + Id int64 `json:"id,string"` + Name string `json:"name"` + DisplayOrder int `json:"displayOrder"` + Hidden bool `json:"hidden"` +} + +func (c *TransactionTag) ToTransactionTagInfoResponse() *TransactionTagInfoResponse { + return &TransactionTagInfoResponse{ + Id: c.TagId, + Name: c.Name, + DisplayOrder: c.DisplayOrder, + Hidden: c.Hidden, + } +} + +type TransactionTagInfoResponseSlice []*TransactionTagInfoResponse + +func (c TransactionTagInfoResponseSlice) Len() int { + return len(c) +} + +func (c TransactionTagInfoResponseSlice) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + +func (c TransactionTagInfoResponseSlice) Less(i, j int) bool { + return c[i].DisplayOrder < c[j].DisplayOrder +} diff --git a/pkg/services/transaction_tags.go b/pkg/services/transaction_tags.go new file mode 100644 index 00000000..04ae8767 --- /dev/null +++ b/pkg/services/transaction_tags.go @@ -0,0 +1,207 @@ +package services + +import ( + "github.com/mayswind/lab/pkg/datastore" + "github.com/mayswind/lab/pkg/errs" + "github.com/mayswind/lab/pkg/models" + "github.com/mayswind/lab/pkg/uuid" + "time" + "xorm.io/xorm" +) + +type TransactionTagService struct { + ServiceUsingDB + ServiceUsingUuid +} + +var ( + TransactionTags = &TransactionTagService{ + ServiceUsingDB: ServiceUsingDB{ + container: datastore.Container, + }, + ServiceUsingUuid: ServiceUsingUuid{ + container: uuid.Container, + }, + } +) + +func (s *TransactionTagService) GetAllTagsByUid(uid int64) ([]*models.TransactionTag, error) { + if uid <= 0 { + return nil, errs.ErrUserIdInvalid + } + + var tags []*models.TransactionTag + err := s.UserDataDB(uid).Where("uid=?", uid).OrderBy("display_order asc").Find(&tags) + + return tags, err +} + +func (s *TransactionTagService) GetTagByTagId(uid int64, tagId int64) (*models.TransactionTag, error) { + if uid <= 0 { + return nil, errs.ErrUserIdInvalid + } + + if tagId <= 0 { + return nil, errs.ErrTransactionTagIdInvalid + } + + tag := &models.TransactionTag{} + has, err := s.UserDataDB(uid).Where("uid=? AND tag_id=?", uid, tagId).Get(tag) + + if err != nil { + return nil, err + } else if !has { + return nil, errs.ErrTransactionTagNotFound + } + + return tag, nil +} + +func (s *TransactionTagService) GetMaxDisplayOrder(uid int64) (int, error) { + if uid <= 0 { + return 0, errs.ErrUserIdInvalid + } + + tag := &models.TransactionTag{} + has, err := s.UserDataDB(uid).Cols("uid", "display_order").Where("uid=?", uid).OrderBy("display_order desc").Limit(1).Get(tag) + + if err != nil { + return 0, err + } + + if has { + return tag.DisplayOrder, nil + } else { + return 0, nil + } +} + +func (s *TransactionTagService) CreateTag(tag *models.TransactionTag) error { + if tag.Uid <= 0 { + return errs.ErrUserIdInvalid + } + + exists, err := s.ExistsTagName(tag.Uid, tag.Name) + + if err != nil { + return err + } else if exists { + return errs.ErrTransactionTagNameAlreadyExists + } + + tag.TagId = s.GenerateUuid(uuid.UUID_TYPE_TAG) + + tag.CreatedUnixTime = time.Now().Unix() + tag.UpdatedUnixTime = time.Now().Unix() + + return s.UserDataDB(tag.Uid).DoTransaction(func(sess *xorm.Session) error { + _, err := sess.Insert(tag) + return err + }) +} + +func (s *TransactionTagService) ModifyTag(tag *models.TransactionTag) error { + if tag.Uid <= 0 { + return errs.ErrUserIdInvalid + } + + exists, err := s.ExistsTagName(tag.Uid, tag.Name) + + if err != nil { + return err + } else if exists { + return errs.ErrTransactionTagNameAlreadyExists + } + + tag.UpdatedUnixTime = time.Now().Unix() + + return s.UserDataDB(tag.Uid).DoTransaction(func(sess *xorm.Session) error { + updatedRows, err := sess.Cols("name", "updated_unix_time").Where("tag_id=? AND uid=?", tag.TagId, tag.Uid).Update(tag) + + if err != nil { + return err + } else if updatedRows < 1 { + return errs.ErrTransactionTagNotFound + } + + return err + }) +} + +func (s *TransactionTagService) HideTag(uid int64, ids []int64, hidden bool) error { + if uid <= 0 { + return errs.ErrUserIdInvalid + } + + now := time.Now().Unix() + + updateModel := &models.TransactionTag{ + Hidden: hidden, + UpdatedUnixTime: now, + } + + return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error { + updatedRows, err := sess.Cols("hidden", "updated_unix_time").In("tag_id", ids).Where("uid=?", uid).Update(updateModel) + + if err != nil { + return err + } else if updatedRows < 1 { + return errs.ErrTransactionTagNotFound + } + + return err + }) +} + +func (s *TransactionTagService) ModifyTagDisplayOrders(uid int64, tags []*models.TransactionTag) error { + if uid <= 0 { + return errs.ErrUserIdInvalid + } + + for i := 0; i < len(tags); i++ { + tags[i].UpdatedUnixTime = time.Now().Unix() + } + + return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error { + for i := 0; i < len(tags); i++ { + tag := tags[i] + updatedRows, err := sess.Cols("display_order", "updated_unix_time").Where("tag_id=? AND uid=?", tag.TagId, uid).Update(tag) + + if err != nil { + return err + } else if updatedRows < 1 { + return errs.ErrTransactionTagNotFound + } + } + + return nil + }) +} + +func (s *TransactionTagService) DeleteTags(uid int64, ids []int64) error { + if uid <= 0 { + return errs.ErrUserIdInvalid + } + + tag := &models.TransactionTag{} + + return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error { + deletedRows, err := sess.In("tag_id", ids).Where("uid=?", uid).Delete(tag) + + if err != nil { + return err + } else if deletedRows < 1 { + return errs.ErrTransactionTagNotFound + } + + return err + }) +} + +func (s *TransactionTagService) ExistsTagName(uid int64, name string) (bool, error) { + if name == "" { + return false, errs.ErrTransactionTagNameIsEmpty + } + + return s.UserDB().Cols("name").Where("uid=? AND name=?", uid, name).Exist(&models.TransactionTag{}) +} diff --git a/src/Mobile.vue b/src/Mobile.vue index 7bacb2e3..1ec9a401 100644 --- a/src/Mobile.vue +++ b/src/Mobile.vue @@ -129,6 +129,15 @@ i.icon.la, i.icon.las, i.icon.lab { align-self: normal !important; } +.list .item-content .input.list-title-input { + margin-top: calc(-1 * var(--f7-list-item-padding-vertical)); + margin-bottom: calc(-1 * var(--f7-list-item-padding-vertical)); +} + +.list .item-content .list-item-valign-middle { + align-self: center; +} + .list .item-content .list-item-checked { font-size: 20px; color: var(--f7-radio-active-color, var(--f7-theme-color)); diff --git a/src/lib/services.js b/src/lib/services.js index c3cdc279..50428336 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -259,6 +259,39 @@ export default { id }); }, + getAllTransactionTags: () => { + return axios.get('v1/transaction/tags/list.json'); + }, + getTransactionTag: ({ id }) => { + return axios.get('v1/transaction/tags/get.json?id=' + id); + }, + addTransactionTag: ({ name }) => { + return axios.post('v1/transaction/tags/add.json', { + name + }); + }, + modifyTransactionTag: ({ id, name }) => { + return axios.post('v1/transaction/tags/modify.json', { + id, + name + }); + }, + hideTransactionTag: ({ id, hidden }) => { + return axios.post('v1/transaction/tags/hide.json', { + id, + hidden + }); + }, + moveTransactionTag: ({ newDisplayOrders }) => { + return axios.post('v1/transaction/tags/move.json', { + newDisplayOrders, + }); + }, + deleteTransactionTag: ({ id }) => { + return axios.post('v1/transaction/tags/delete.json', { + id + }); + }, getLatestExchangeRates: () => { return axios.get('v1/exchange_rates/latest.json'); }, diff --git a/src/locales/en.js b/src/locales/en.js index 9e9996b7..f7263126 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -307,6 +307,10 @@ export default { 'transaction category type is invalid': 'Transaction category type is invalid', 'parent transaction category not found': 'Parent transaction category is not found', 'cannot add to secondary transaction category': 'Cannot add transaction category to secondary transaction category', + 'transaction tag id is invalid': 'Transaction tag ID is invalid', + 'transaction tag not found': 'Transaction tag is not found', + 'transaction tag name is empty': 'Transaction tag title is empty', + 'transaction tag name already exists': 'Transaction tag title already exists', }, 'parameter': { 'id': 'ID', @@ -520,6 +524,15 @@ export default { 'You have added a new category': 'You have added a new category', 'You have added default categories': 'You have added default categories', 'You have saved this category': 'You have saved this category', + 'Transaction Tags': 'Transaction Tags', + 'Tag Title': 'Tag Title', + 'No available tag': 'No available tag', + 'Unable to get tag list': 'Unable to get tag list', + 'Unable to move tag': 'Unable to move tag', + 'Unable to hide this tag': 'Unable to hide this tag', + 'Unable to unhide this tag': 'Unable to unhide this tag', + 'Are you sure you want to delete this tag?': 'Are you sure you want to delete this tag?', + 'Unable to delete this tag': 'Unable to delete this tag', 'Are you sure you want to logout from this session?': 'Are you sure you want to logout from this session?', 'Unable to logout from this session': 'Unable to logout from this session', 'Are you sure you want to logout all other sessions?': 'Are you sure you want to logout all other sessions?', diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index 2a72229c..f69acba8 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -307,6 +307,10 @@ export default { 'transaction category type is invalid': '交易分类类型无效', 'parent transaction category not found': '父级交易分类不存在', 'cannot add to secondary transaction category': '不能在二级交易分类中添加', + 'transaction tag id is invalid': '交易标签ID无效', + 'transaction tag not found': '交易标签不存在', + 'transaction tag name is empty': '交易标签标题不能为空', + 'transaction tag name already exists': '交易标签标题已经存在', }, 'parameter': { 'id': 'ID', @@ -520,6 +524,15 @@ export default { 'You have added a new category': '您已经添加新分类', 'You have added default categories': '您已经添加默认分类', 'You have saved this category': '您已经保存该分类', + 'Transaction Tags': '交易标签', + 'Tag Title': '标签标题', + 'No available tag': '没有可用的标签', + 'Unable to get tag list': '无法获取标签列表', + 'Unable to move tag': '无法移动标签', + 'Unable to hide this tag': '无法隐藏该标签', + 'Unable to unhide this tag': '无法取消隐藏该标签', + 'Are you sure you want to delete this tag?': '您确定要删除该标签?', + 'Unable to delete this tag': '无法删除该标签', 'Are you sure you want to logout from this session?': '您确定要退出该会话?', 'Unable to logout from this session': '无法退出该会话', 'Are you sure you want to logout all other sessions?': '您确定要退出其他所有会话?', diff --git a/src/router/mobile.js b/src/router/mobile.js index b217ada7..064c6166 100644 --- a/src/router/mobile.js +++ b/src/router/mobile.js @@ -27,6 +27,8 @@ import CategoryListPage from "../views/mobile/categories/List.vue"; import CategoryEditPage from "../views/mobile/categories/Edit.vue"; import CategoryDefaultPage from "../views/mobile/categories/Default.vue"; +import TagListPage from "../views/mobile/tags/List.vue"; + function checkLogin(to, from, resolve, reject) { const router = this; @@ -210,6 +212,11 @@ const routes = [ component: CategoryDefaultPage, beforeEnter: checkLogin }, + { + path: '/tag/list', + component: TagListPage, + beforeEnter: checkLogin + }, { path: '(.*)', redirect: '/' diff --git a/src/views/mobile/Settings.vue b/src/views/mobile/Settings.vue index 61b364a4..7ed2418a 100644 --- a/src/views/mobile/Settings.vue +++ b/src/views/mobile/Settings.vue @@ -8,6 +8,7 @@ + {{ $t('Log Out') }} diff --git a/src/views/mobile/tags/List.vue b/src/views/mobile/tags/List.vue new file mode 100644 index 00000000..7ae6873c --- /dev/null +++ b/src/views/mobile/tags/List.vue @@ -0,0 +1,500 @@ + + +