mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-14 06:57:35 +08:00
supports local file system object storage and use it as the default avatar provider
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
const supportedImageExtensions = '.jpg,.jpeg,.png,.gif,.webp';
|
||||
|
||||
export default {
|
||||
supportedImageExtensions: supportedImageExtensions
|
||||
}
|
||||
@@ -2,12 +2,14 @@ import axios from 'axios';
|
||||
|
||||
import apiConstants from '@/consts/api.js';
|
||||
import userState from './userstate.js';
|
||||
import { isBoolean } from './common.js';
|
||||
import {
|
||||
getGoogleMapAPIKey,
|
||||
getBaiduMapAK,
|
||||
getAmapApplicationKey
|
||||
} from './server_settings.js';
|
||||
import { getTimezoneOffsetMinutes } from './datetime.js';
|
||||
import { generateRandomUUID } from './misc.js';
|
||||
|
||||
let needBlockRequest = false;
|
||||
let blockedRequests = [];
|
||||
@@ -192,6 +194,14 @@ export default {
|
||||
incomeAmountColor
|
||||
});
|
||||
},
|
||||
updateAvatar: ({ avatarFile }) => {
|
||||
return axios.postForm('v1/users/avatar/update.json', {
|
||||
avatar: avatarFile
|
||||
});
|
||||
},
|
||||
removeAvatar: () => {
|
||||
return axios.post('v1/users/avatar/remove.json');
|
||||
},
|
||||
resendVerifyEmailByLoginedUser: () => {
|
||||
return axios.post('v1/users/verify_email/resend.json');
|
||||
},
|
||||
@@ -552,5 +562,27 @@ export default {
|
||||
},
|
||||
generateAmapApiInternalProxyUrl: () => {
|
||||
return `${window.location.origin}${apiConstants.baseAmapApiProxyUrlPath}`;
|
||||
},
|
||||
getInternalAvatarUrlWithToken(avatarUrl, disableBrowserCache) {
|
||||
if (!avatarUrl) {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
const params = [];
|
||||
params.push('token=' + userState.getToken());
|
||||
|
||||
if (disableBrowserCache) {
|
||||
if (isBoolean(disableBrowserCache)) {
|
||||
params.push('_nocache=' + generateRandomUUID());
|
||||
} else {
|
||||
params.push('_nocache=' + disableBrowserCache);
|
||||
}
|
||||
}
|
||||
|
||||
if (avatarUrl.indexOf('?') >= 0) {
|
||||
return avatarUrl + '&' + params.join('&');
|
||||
} else {
|
||||
return avatarUrl + '?' + params.join('&');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -592,6 +592,7 @@ export default {
|
||||
'not implemented': 'Not implemented',
|
||||
'system is busy': 'System is busy',
|
||||
'not supported': 'Not supported',
|
||||
'image type not supported': 'Image type is not supported',
|
||||
'database operation failed': 'Database operation failed',
|
||||
'SMTP server is not enabled': 'SMTP server is not enabled',
|
||||
'incomplete or incorrect submission': 'Incomplete or incorrect submission',
|
||||
@@ -624,6 +625,9 @@ export default {
|
||||
'email validation not allowed': 'Email validation is not allowed',
|
||||
'decimal separator and digit grouping symbol cannot be equal': 'Decimal separator and digit grouping symbol cannot be equal',
|
||||
'user default account is hidden': 'Cannot set hidden account as default account',
|
||||
'no user avatar': 'There is no user avatar file',
|
||||
'user avatar is empty': 'User avatar file is empty',
|
||||
'user avatar not exists': 'User avatar does not exist',
|
||||
'unauthorized access': 'Unauthorized access',
|
||||
'current token is invalid': 'Current token is invalid',
|
||||
'current token is expired': 'Current token is expired',
|
||||
@@ -1159,6 +1163,8 @@ export default {
|
||||
'Basic Settings': 'Basic Settings',
|
||||
'Security Settings': 'Security Settings',
|
||||
'Two-Factor Authentication Settings': 'Two-Factor Authentication Settings',
|
||||
'Update Avatar': 'Update Avatar',
|
||||
'Remove Avatar': 'Remove Avatar',
|
||||
'(Verified)': '(Verified)',
|
||||
'(Not Verified)': '(Not Verified)',
|
||||
'Email address is verified': 'Email address is verified',
|
||||
@@ -1170,6 +1176,11 @@ export default {
|
||||
'Please enter your current password when modifying your password': 'Please enter your current password when modifying your password',
|
||||
'Nothing has been modified': 'Nothing has been modified',
|
||||
'Your profile has been successfully updated': 'Your profile has been successfully updated',
|
||||
'Unable to update user avatar': 'Unable to update user avatar',
|
||||
'Your avatar has been successfully updated': 'Your avatar has been successfully updated',
|
||||
'Are you sure you want to remove avatar?': 'Are you sure you want to remove avatar?',
|
||||
'Unable to remove user avatar': 'Unable to remove user avatar',
|
||||
'Your avatar has been successfully removed': 'Your avatar has been successfully removed',
|
||||
'Unable to update user profile': 'Unable to update user profile',
|
||||
'After changing the password, other devices will be logged out. Please use the new password to log in on other devices.': 'After changing the password, other devices will be logged out. Please use the new password to log in on other devices.',
|
||||
'Data Management': 'Data Management',
|
||||
|
||||
@@ -592,6 +592,7 @@ export default {
|
||||
'not implemented': '未实现',
|
||||
'system is busy': '系统繁忙',
|
||||
'not supported': '不支持',
|
||||
'image type not supported': '图片类型不支持',
|
||||
'database operation failed': '数据库操作失败',
|
||||
'SMTP server is not enabled': 'SMTP 服务器没有启用',
|
||||
'incomplete or incorrect submission': '提交不完整或不正确',
|
||||
@@ -624,6 +625,9 @@ export default {
|
||||
'email validation not allowed': '不允许邮箱验证',
|
||||
'decimal separator and digit grouping symbol cannot be equal': '小数点和数字分组符号不能相同',
|
||||
'user default account is hidden': '不能把隐藏账户设置为默认账户',
|
||||
'no user avatar': '没有用户头像文件',
|
||||
'user avatar is empty': '用户头像文件为空',
|
||||
'user avatar not exists': '用户头像不存在',
|
||||
'unauthorized access': '未授权的登录',
|
||||
'current token is invalid': '当前认证令牌无效',
|
||||
'current token is expired': '当前认证令牌已过期',
|
||||
@@ -1159,6 +1163,8 @@ export default {
|
||||
'Basic Settings': '基本设置',
|
||||
'Security Settings': '安全设置',
|
||||
'Two-Factor Authentication Settings': '两步验证设置',
|
||||
'Update Avatar': '更新头像',
|
||||
'Remove Avatar': '删除头像',
|
||||
'(Verified)': '(已验证)',
|
||||
'(Not Verified)': '(未验证)',
|
||||
'Email address is verified': '邮箱地址已验证',
|
||||
@@ -1170,6 +1176,11 @@ export default {
|
||||
'Please enter your current password when modifying your password': '修改密码时请输入您的当前密码',
|
||||
'Nothing has been modified': '没有修改的项目',
|
||||
'Your profile has been successfully updated': '您的用户信息更新成功',
|
||||
'Unable to update user avatar': '无法更新用户头像',
|
||||
'Your avatar has been successfully updated': '您的头像更新成功',
|
||||
'Are you sure you want to remove avatar?': '您确定要删除头像?',
|
||||
'Unable to remove user avatar': '无法删除用户头像',
|
||||
'Your avatar has been successfully removed': '您的用户头像删除成功',
|
||||
'Unable to update user profile': '无法更新用户信息',
|
||||
'After changing the password, other devices will be logged out. Please use the new password to log in on other devices.': '密码修改后,其他设备将会退出登录,请使用新密码在其他设备上重新登录。',
|
||||
'Data Management': '数据管理',
|
||||
|
||||
+56
-1
@@ -18,7 +18,7 @@ export const useUserStore = defineStore('user', {
|
||||
},
|
||||
currentUserAvatar(state) {
|
||||
const userInfo = state.currentUserBasicInfo || {};
|
||||
return userInfo.avatar || null;
|
||||
return state.getUserAvatarUrl(userInfo, false);
|
||||
},
|
||||
currentUserDefaultAccountId(state) {
|
||||
const userInfo = state.currentUserBasicInfo || {};
|
||||
@@ -126,6 +126,54 @@ export const useUserStore = defineStore('user', {
|
||||
});
|
||||
});
|
||||
},
|
||||
updateUserAvatar({ avatarFile }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.updateAvatar({ avatarFile }).then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to update user avatar' });
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to update user avatar', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to update user avatar' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
removeUserAvatar() {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.removeAvatar().then(response => {
|
||||
const data = response.data;
|
||||
|
||||
if (!data || !data.success || !data.result) {
|
||||
reject({ message: 'Unable to remove user avatar' });
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(data.result);
|
||||
}).catch(error => {
|
||||
logger.error('failed to remove user avatar', error);
|
||||
|
||||
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||
reject({ error: error.response.data });
|
||||
} else if (!error.processed) {
|
||||
reject({ message: 'Unable to remove user avatar' });
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
getUserDataStatistics() {
|
||||
return new Promise((resolve, reject) => {
|
||||
services.getUserDataStatistics().then(response => {
|
||||
@@ -178,5 +226,12 @@ export const useUserStore = defineStore('user', {
|
||||
});
|
||||
});
|
||||
},
|
||||
getUserAvatarUrl(userInfo, disableBrowserCache) {
|
||||
if (!userInfo || !userInfo.avatar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return services.getInternalAvatarUrlWithToken(userInfo.avatar, disableBrowserCache);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,15 +8,21 @@
|
||||
</template>
|
||||
|
||||
<v-card-text class="d-flex">
|
||||
<v-avatar rounded="lg" color="primary" variant="tonal" size="100" class="me-4">
|
||||
<v-img :src="oldProfile.avatar" v-if="oldProfile.avatar">
|
||||
<v-avatar rounded="lg" color="primary" variant="tonal" size="100" class="me-4" :class="{ 'cursor-pointer': oldProfile.avatarProvider === 'internal' }">
|
||||
<v-img :src="currentUserAvatar" v-if="currentUserAvatar">
|
||||
<template #placeholder>
|
||||
<div class="d-flex align-center justify-center fill-height">
|
||||
<v-icon size="48" :icon="icons.user"/>
|
||||
</div>
|
||||
</template>
|
||||
</v-img>
|
||||
<v-icon size="48" :icon="icons.user" v-else-if="!oldProfile.avatar"/>
|
||||
<v-icon size="48" :icon="icons.user" v-else-if="!currentUserAvatar"/>
|
||||
<v-menu activator="parent" width="200" location="bottom" offset="14px" v-if="oldProfile.avatarProvider === 'internal'">
|
||||
<v-list>
|
||||
<v-list-item :disabled="saving" :title="$t('Update Avatar')" @click="showOpenAvatarDialog"></v-list-item>
|
||||
<v-list-item :disabled="!currentUserAvatar || saving" :title="$t('Remove Avatar')" @click="removeAvatar"></v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-avatar>
|
||||
<div class="d-flex flex-column justify-center gap-3">
|
||||
<div class="d-flex text-body-1">
|
||||
@@ -301,6 +307,7 @@
|
||||
|
||||
<confirm-dialog ref="confirmDialog"/>
|
||||
<snack-bar ref="snackbar" />
|
||||
<input ref="avatarInput" type="file" style="display: none" :accept="supportedImageExtensions" @change="updateAvatar($event)" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -312,7 +319,9 @@ import { useAccountsStore } from '@/stores/account.js';
|
||||
import { useOverviewStore } from '@/stores/overview.js';
|
||||
|
||||
import datetimeConstants from '@/consts/datetime.js';
|
||||
import fileConstants from '@/consts/file.js';
|
||||
import { getNameByKeyValue } from '@/lib/common.js';
|
||||
import { generateRandomUUID } from '@/lib/misc.js';
|
||||
import { getCategorizedAccounts } from '@/lib/account.js';
|
||||
import { isUserVerifyEmailEnabled } from '@/lib/server_settings.js';
|
||||
import { setExpenseAndIncomeAmountColor } from '@/lib/ui.js';
|
||||
@@ -366,6 +375,7 @@ export default {
|
||||
expenseAmountColor: 0,
|
||||
incomeAmountColor: 0
|
||||
},
|
||||
avatarNoCacheId: '',
|
||||
emailVerified: false,
|
||||
loading: true,
|
||||
resending: false,
|
||||
@@ -428,6 +438,12 @@ export default {
|
||||
allTransactionEditScopeTypes() {
|
||||
return this.$locale.getAllTransactionEditScopeTypes();
|
||||
},
|
||||
supportedImageExtensions() {
|
||||
return fileConstants.supportedImageExtensions;
|
||||
},
|
||||
currentUserAvatar() {
|
||||
return this.userStore.getUserAvatarUrl(this.oldProfile, this.avatarNoCacheId);
|
||||
},
|
||||
isUserVerifyEmailEnabled() {
|
||||
return isUserVerifyEmailEnabled();
|
||||
},
|
||||
@@ -558,6 +574,61 @@ export default {
|
||||
}
|
||||
});
|
||||
},
|
||||
showOpenAvatarDialog() {
|
||||
this.$refs.avatarInput.click();
|
||||
},
|
||||
updateAvatar(event) {
|
||||
if (!event || !event.target || !event.target.files || !event.target.files.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const avatarFile = event.target.files[0];
|
||||
|
||||
event.target.value = null;
|
||||
|
||||
self.saving = true;
|
||||
|
||||
self.userStore.updateUserAvatar({ avatarFile }).then(response => {
|
||||
self.saving = false;
|
||||
|
||||
if (response) {
|
||||
self.avatarNoCacheId = generateRandomUUID();
|
||||
self.setCurrentUserProfile(response);
|
||||
}
|
||||
|
||||
self.$refs.snackbar.showMessage('Your avatar has been successfully updated');
|
||||
}).catch(error => {
|
||||
self.saving = false;
|
||||
|
||||
if (!error.processed) {
|
||||
self.$refs.snackbar.showError(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
removeAvatar() {
|
||||
const self = this;
|
||||
|
||||
self.$refs.confirmDialog.open('Are you sure you want to remove avatar?').then(() => {
|
||||
self.saving = true;
|
||||
|
||||
self.userStore.removeUserAvatar().then(response => {
|
||||
self.saving = false;
|
||||
|
||||
if (response) {
|
||||
self.setCurrentUserProfile(response);
|
||||
}
|
||||
|
||||
self.$refs.snackbar.showMessage('Your profile has been successfully updated');
|
||||
}).catch(error => {
|
||||
self.saving = false;
|
||||
|
||||
if (!error.processed) {
|
||||
self.$refs.snackbar.showError(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
reset() {
|
||||
this.setCurrentUserProfile(this.oldProfile);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user