From b4bd83e7a006b29e812f03a6a1f6e42d104affc3 Mon Sep 17 00:00:00 2001 From: MaysWind Date: Tue, 17 Nov 2020 01:54:26 +0800 Subject: [PATCH] support account color --- cmd/webserver.go | 1 + pkg/api/accounts.go | 3 + pkg/models/account.go | 5 ++ pkg/services/accounts.go | 2 +- pkg/utils/regexps.go | 5 ++ pkg/validators/rgb_color.go | 17 +++++ src/consts/color.js | 22 ++++++ src/lib/services.js | 6 +- src/locales/en.js | 3 + src/locales/zh_Hans.js | 3 + src/mobile-main.js | 2 + src/views/mobile/accounts/AccountEdit.vue | 89 ++++++++++++++++++++++- src/views/mobile/accounts/AccountList.vue | 4 +- 13 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 pkg/validators/rgb_color.go create mode 100644 src/consts/color.js diff --git a/cmd/webserver.go b/cmd/webserver.go index 4e866789..17dc8a13 100644 --- a/cmd/webserver.go +++ b/cmd/webserver.go @@ -74,6 +74,7 @@ func startWebServer(c *cli.Context) error { _ = v.RegisterValidation("validUsername", validators.ValidUsername) _ = v.RegisterValidation("validEmail", validators.ValidEmail) _ = v.RegisterValidation("validCurrency", validators.ValidCurrency) + _ = v.RegisterValidation("validRGBColor", validators.ValidRGBColor) } router.NoRoute(bindApi(api.Default.ApiNotFound)) diff --git a/pkg/api/accounts.go b/pkg/api/accounts.go index 23c1cb75..0b071dd1 100644 --- a/pkg/api/accounts.go +++ b/pkg/api/accounts.go @@ -343,6 +343,7 @@ func (a *AccountsApi) createNewAccount(uid int64, accountCreateReq *models.Accou Category: accountCreateReq.Category, Type: accountCreateReq.Type, Icon: accountCreateReq.Icon, + Color: accountCreateReq.Color, Currency: accountCreateReq.Currency, Comment: accountCreateReq.Comment, } @@ -369,6 +370,7 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc Name: accountModifyReq.Name, Category: accountModifyReq.Category, Icon: accountModifyReq.Icon, + Color: accountModifyReq.Color, Comment: accountModifyReq.Comment, Hidden: accountModifyReq.Hidden, } @@ -376,6 +378,7 @@ func (a *AccountsApi) getToUpdateAccount(uid int64, accountModifyReq *models.Acc if newAccount.Name != oldAccount.Name || newAccount.Category != oldAccount.Category || newAccount.Icon != oldAccount.Icon || + newAccount.Color != oldAccount.Color || newAccount.Comment != oldAccount.Comment || newAccount.Hidden != oldAccount.Hidden { return newAccount diff --git a/pkg/models/account.go b/pkg/models/account.go index 1b52835d..a5abd00d 100644 --- a/pkg/models/account.go +++ b/pkg/models/account.go @@ -32,6 +32,7 @@ type Account struct { Name string `xorm:"VARCHAR(32) NOT NULL"` DisplayOrder int `xorm:"INDEX(IDX_account_uid_deleted_parent_account_id_order) NOT NULL"` Icon int64 `xorm:"NOT NULL"` + Color string `xorm:"VARCHAR(6) NOT NULL"` Currency string `xorm:"VARCHAR(3) NOT NULL"` Balance int64 `xorm:"NOT NULL"` Comment string `xorm:"VARCHAR(255) NOT NULL"` @@ -46,6 +47,7 @@ type AccountCreateRequest struct { Category AccountCategory `json:"category" binding:"required"` Type AccountType `json:"type" binding:"required"` Icon int64 `json:"icon,string" binding:"required,min=1"` + Color string `json:"color" binding:"required,len=6,validRGBColor"` Currency string `json:"currency" binding:"required,len=3,validCurrency"` Comment string `json:"comment" binding:"max=255"` SubAccounts []*AccountCreateRequest `json:"subAccounts" binding:"omitempty"` @@ -60,6 +62,7 @@ type AccountModifyRequest struct { Name string `json:"name" binding:"required,notBlank,max=32"` Category AccountCategory `json:"category" binding:"required"` Icon int64 `json:"icon,string" binding:"min=1"` + Color string `json:"color" binding:"required,len=6,validRGBColor"` Comment string `json:"comment" binding:"max=255"` Hidden bool `json:"hidden"` SubAccounts []*AccountModifyRequest `json:"subAccounts" binding:"omitempty"` @@ -90,6 +93,7 @@ type AccountInfoResponse struct { Category AccountCategory `json:"category"` Type AccountType `json:"type"` Icon int64 `json:"icon,string"` + Color string `json:"color"` Currency string `json:"currency"` Balance int64 `json:"balance"` Comment string `json:"comment"` @@ -106,6 +110,7 @@ func (a *Account) ToAccountInfoResponse() *AccountInfoResponse { Category: a.Category, Type: a.Type, Icon: a.Icon, + Color: a.Color, Currency: a.Currency, Balance: a.Balance, Comment: a.Comment, diff --git a/pkg/services/accounts.go b/pkg/services/accounts.go index 0e3bff2e..11525c06 100644 --- a/pkg/services/accounts.go +++ b/pkg/services/accounts.go @@ -151,7 +151,7 @@ func (s *AccountService) ModifyAccounts(uid int64, accounts []*models.Account) e return s.UserDataDB(uid).DoTransaction(func(sess *xorm.Session) error { for i := 0; i < len(accounts); i++ { account := accounts[i] - _, err := sess.Cols("name", "category", "icon", "comment", "hidden", "updated_unix_time").Where("account_id=? AND uid=? AND deleted=?", account.AccountId, uid, false).Update(account) + _, err := sess.Cols("name", "category", "icon", "color", "comment", "hidden", "updated_unix_time").Where("account_id=? AND uid=? AND deleted=?", account.AccountId, uid, false).Update(account) if err != nil { return err diff --git a/pkg/utils/regexps.go b/pkg/utils/regexps.go index 99b50813..da15073f 100644 --- a/pkg/utils/regexps.go +++ b/pkg/utils/regexps.go @@ -5,6 +5,7 @@ import "regexp" var ( UsernamePattern = regexp.MustCompile("^(?i)[a-z0-9_-]+$") EmailPattern = regexp.MustCompile("^(?i)(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])$") + RGBColorPattern = regexp.MustCompile("^(?i)([0-9a-f]{6}|[0-9a-f]{3})$") ) func IsValidUsername(username string) bool { @@ -14,3 +15,7 @@ func IsValidUsername(username string) bool { func IsValidEmail(email string) bool { return EmailPattern.MatchString(email) } + +func IsValidRGBColor(color string) bool { + return RGBColorPattern.MatchString(color) +} diff --git a/pkg/validators/rgb_color.go b/pkg/validators/rgb_color.go new file mode 100644 index 00000000..2ec6e3fd --- /dev/null +++ b/pkg/validators/rgb_color.go @@ -0,0 +1,17 @@ +package validators + +import ( + "github.com/go-playground/validator/v10" + + "github.com/mayswind/lab/pkg/utils" +) + +func ValidRGBColor(fl validator.FieldLevel) bool { + if value, ok := fl.Field().Interface().(string); ok { + if utils.IsValidRGBColor(value) { + return true + } + } + + return false +} diff --git a/src/consts/color.js b/src/consts/color.js new file mode 100644 index 00000000..ea22eb2a --- /dev/null +++ b/src/consts/color.js @@ -0,0 +1,22 @@ +const defaultAccountColor = '000000'; +const allAccountColors = [ + '000000', // black + '8e8e93', // gray + 'ff3b30', // red + 'ff2d55', // pink + 'ff6b22', // deep orange + 'ff9500', // orange + 'ffcc00', // yellow + 'cddc39', // lime + '009688', // teal + '4cd964', // green + '5ac8fa', // light blue + '2196f3', // blue + '673ab7', // deep purple + '9c27b0', // purple +]; + +export default { + allAccountColors: allAccountColors, + defaultAccountColor: defaultAccountColor, +}; diff --git a/src/lib/services.js b/src/lib/services.js index d0aab51d..054d3dd0 100644 --- a/src/lib/services.js +++ b/src/lib/services.js @@ -169,23 +169,25 @@ export default { getAccount: ({ id }) => { return axios.get('v1/accounts/get.json?id=' + id); }, - addAccount: ({ category, type, name, icon, currency, comment, subAccounts }) => { + addAccount: ({ category, type, name, icon, color, currency, comment, subAccounts }) => { return axios.post('v1/accounts/add.json', { category, type, name, icon, + color, currency, comment, subAccounts }); }, - modifyAccount: ({ id, category, name, icon, comment, hidden, subAccounts }) => { + modifyAccount: ({ id, category, name, icon, color, comment, hidden, subAccounts }) => { return axios.post('v1/accounts/modify.json', { id, category, name, icon, + color, comment, hidden, subAccounts diff --git a/src/locales/en.js b/src/locales/en.js index 554fb84a..74266ffe 100644 --- a/src/locales/en.js +++ b/src/locales/en.js @@ -224,6 +224,7 @@ export default { 'nickname': 'Nickname', 'oldPassword': 'Current Password', 'defaultCurrency': 'Default Currency', + 'color': 'Color', }, 'parameterizedError': { 'parameter invalid': '{parameter} is invalid', @@ -328,6 +329,8 @@ export default { 'Your sub account name': 'Your sub account name', 'Account Icon': 'Account Icon', 'Sub Account Icon': 'Sub Account Icon', + 'Account Color': 'Account Color', + 'Sub Account Color': 'Sub Account Color', 'Currency': 'Currency', 'Description': 'Description', 'Your account description (optional)': 'Your account description (optional)', diff --git a/src/locales/zh_Hans.js b/src/locales/zh_Hans.js index 0361f0f7..00fbb4a6 100644 --- a/src/locales/zh_Hans.js +++ b/src/locales/zh_Hans.js @@ -224,6 +224,7 @@ export default { 'nickname': '昵称', 'oldPassword': '当前密码', 'defaultCurrency': '默认货币', + 'color': '颜色', }, 'parameterizedError': { 'parameter invalid': '{parameter}无效', @@ -328,6 +329,8 @@ export default { 'Your sub account name': '你的子账户名称', 'Account Icon': '账户图标', 'Sub Account Icon': '子账户图标', + 'Account Color': '账户颜色', + 'Sub Account Color': '子账户颜色', 'Currency': '货币', 'Description': '描述', 'Your account description (optional)': '你的账户描述 (可选)', diff --git a/src/mobile-main.js b/src/mobile-main.js index 428fb9d1..297462aa 100644 --- a/src/mobile-main.js +++ b/src/mobile-main.js @@ -14,6 +14,7 @@ import 'framework7-icons'; import { getAllLanguages, getLanguage, getDefaultLanguage, getI18nOptions, getLocalizedError, getLocalizedErrorParameters } from './lib/i18n.js'; import currency from './consts/currency.js'; +import colors from './consts/color.js'; import icons from './consts/icon.js'; import account from './consts/account.js'; import version from './lib/version.js'; @@ -36,6 +37,7 @@ const i18n = new VueI18n(getI18nOptions()); Vue.prototype.$version = version.getVersion; Vue.prototype.$constants = { currency: currency, + colors: colors, icons: icons, account: account, }; diff --git a/src/views/mobile/accounts/AccountEdit.vue b/src/views/mobile/accounts/AccountEdit.vue index fdaca2a0..7d6e8cc6 100644 --- a/src/views/mobile/accounts/AccountEdit.vue +++ b/src/views/mobile/accounts/AccountEdit.vue @@ -50,6 +50,7 @@ + @@ -70,7 +71,12 @@ - + + + + + - + + + + + - + + + + + + + + +
+
+ +
+
+ + + + + + + + + + + + + +
@@ -223,14 +261,17 @@ export default { type: self.$constants.account.allAccountTypes.SingleAccount.toString(), name: '', icon: self.$constants.icons.defaultAccountIconId, + color: self.$constants.colors.defaultAccountColor, currency: self.$user.getUserInfo() ? self.$user.getUserInfo().defaultCurrency : self.$t('default.currency'), comment: '', visible: true }, subAccounts: [], accountChoosingIcon: null, + accountChoosingColor: null, submitting: false, - showIconSelection: false + showIconSelection: false, + showColorSelection: false }; }, computed: { @@ -279,6 +320,24 @@ export default { return ret; }, + allAccountColorRows() { + const allAccountColors = this.$constants.colors.allAccountColors; + const colorPerRow = 7; + const ret = []; + let rowCount = -1; + + for (let i = 0; i < allAccountColors.length; i++) { + if (i % colorPerRow === 0) { + ret[++rowCount] = []; + } + + ret[rowCount].push({ + color: allAccountColors[i] + }); + } + + return ret; + }, allCurrencies() { return this.$getAllCurrencies(); } @@ -310,6 +369,7 @@ export default { self.account.type = account.type.toString(); self.account.name = account.name; self.account.icon = account.icon; + self.account.color = account.color; self.account.currency = account.currency; self.account.comment = account.comment; self.account.visible = !account.hidden; @@ -324,6 +384,7 @@ export default { type: subAccount.type.toString(), name: subAccount.name, icon: subAccount.icon, + color: subAccount.color, currency: subAccount.currency, comment: subAccount.comment, visible: !subAccount.hidden @@ -360,6 +421,7 @@ export default { type: null, name: '', icon: this.account.icon, + color: this.account.color, currency: self.$user.getUserInfo() ? self.$user.getUserInfo().defaultCurrency : self.$t('default.currency'), comment: '' }); @@ -388,6 +450,23 @@ export default { this.accountChoosingIcon = null; this.showIconSelection = false; }, + showColorSelectionSheet(account) { + this.accountChoosingColor = account; + this.showColorSelection = true; + }, + setSelectedColor(color) { + if (!this.accountChoosingColor) { + return; + } + + this.accountChoosingColor.color = color; + this.accountChoosingColor = null; + this.showColorSelection = false; + }, + hideColorSelectionSheet() { + this.accountChoosingColor = null; + this.showColorSelection = false; + }, save() { const self = this; const router = self.$f7router; @@ -412,6 +491,7 @@ export default { type: self.$constants.account.allAccountTypes.SingleAccount, name: subAccount.name, icon: subAccount.icon, + color: subAccount.color, currency: subAccount.currency, comment: subAccount.comment }; @@ -430,6 +510,7 @@ export default { type: parseInt(self.account.type), name: self.account.name, icon: self.account.icon, + color: self.account.color, currency: self.account.type === self.$constants.account.allAccountTypes.SingleAccount.toString() ? self.account.currency : self.$constants.currency.parentAccountCurrencyPlacehodler, comment: self.account.comment, subAccounts: self.account.type === self.$constants.account.allAccountTypes.SingleAccount.toString() ? null : subAccounts, diff --git a/src/views/mobile/accounts/AccountList.vue b/src/views/mobile/accounts/AccountList.vue index fb4b08ab..22a05bbb 100644 --- a/src/views/mobile/accounts/AccountList.vue +++ b/src/views/mobile/accounts/AccountList.vue @@ -82,7 +82,7 @@ >
- + @@ -96,7 +96,7 @@ :title="subAccount.name" :after="accountBalance(subAccount) | currency(subAccount.currency)" link="#" > - +