diff --git a/back_end/saolei/msuser/views.py b/back_end/saolei/msuser/views.py index 02ef428..baa2116 100644 --- a/back_end/saolei/msuser/views.py +++ b/back_end/saolei/msuser/views.py @@ -1,15 +1,13 @@ import logging logger = logging.getLogger('userprofile') -from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from .forms import UserUpdateRealnameForm, UserUpdateAvatarForm, UserUpdateSignatureForm # from .models import VideoModel, ExpandVideoModel -from django.http import HttpResponse, JsonResponse, HttpResponseNotAllowed, HttpResponseBadRequest, HttpResponseNotFound +from django.http import JsonResponse, HttpResponseBadRequest, HttpResponseNotFound # from asgiref.sync import sync_to_async import json from utils import ComplexEncoder # from django.core.paginator import Paginator -from msuser.models import UserMS from userprofile.models import UserProfile import base64 import decimal @@ -18,11 +16,9 @@ cache = get_redis_connection("saolei_website") from django.conf import settings import os -from django.utils import timezone -from datetime import datetime, timedelta -from utils import verify_text from django_ratelimit.decorators import ratelimit from django.views.decorators.http import require_GET, require_POST +from userprofile.utils import user_metadata from config.global_settings import * @@ -50,24 +46,8 @@ def get_info(request): user.popularity += 1 user.save(update_fields=["popularity"]) - - if user.avatar: - avatar_path = os.path.join(settings.MEDIA_ROOT, urllib.parse.unquote(user.avatar.url)[7:]) - image_data = open(avatar_path, "rb").read() - image_data = base64.b64encode(image_data).decode() - else: - image_data = None - response = {"id": user_id, - "username": user.username, - "realname": user.realname, - "avatar": image_data, - "signature": user.signature, - "popularity": user.popularity, - "identifiers": user.userms.identifiers, - "is_banned": user.is_banned, - "country": user.country - } - return JsonResponse(response) + + return JsonResponse(user_metadata(user)) # 获取我的地盘里的姓名、全部纪录 diff --git a/back_end/saolei/userprofile/utils.py b/back_end/saolei/userprofile/utils.py index 79d2d7d..8932a05 100644 --- a/back_end/saolei/userprofile/utils.py +++ b/back_end/saolei/userprofile/utils.py @@ -1,6 +1,12 @@ from captcha.models import CaptchaStore from django.utils import timezone from .models import EmailVerifyRecord +from .models import UserProfile +from videomanager.models import VideoModel +import os +from django.conf import settings +import urllib.parse +import base64 # 验证验证码 def judge_captcha(captchaStr, captchaHashkey): @@ -25,3 +31,25 @@ def judge_email_verification(email, email_captcha, emailHashkey): EmailVerifyRecord.objects.filter(hashkey=emailHashkey).delete() return False return get_email_captcha.code == email_captcha and get_email_captcha.email == email + +def user_metadata(user: UserProfile): + if user.avatar: + avatar_path = os.path.join(settings.MEDIA_ROOT, urllib.parse.unquote(user.avatar.url)[7:]) + image_data = open(avatar_path, "rb").read() + image_data = base64.b64encode(image_data).decode() + else: + image_data = None + + videos = VideoModel.objects.filter(player=user).values('id', 'upload_time', "level", "mode", "timems", "bv", "state", "software") + return {"id": user.id, + "username": user.username, + "realname": user.realname, + "avatar": image_data, + "signature": user.signature, + "popularity": user.popularity, + "identifiers": user.userms.identifiers, + "is_banned": user.is_banned, + "is_staff": user.is_staff, + "country": user.country, + "videos": list(videos), + } \ No newline at end of file diff --git a/back_end/saolei/userprofile/views.py b/back_end/saolei/userprofile/views.py index 519fbf9..f401410 100644 --- a/back_end/saolei/userprofile/views.py +++ b/back_end/saolei/userprofile/views.py @@ -14,7 +14,7 @@ from .decorators import staff_required from django.utils import timezone from config.flags import EMAIL_SKIP -from .utils import judge_captcha, judge_email_verification +from .utils import judge_captcha, judge_email_verification, user_metadata # Create your views here. @@ -42,11 +42,10 @@ def user_login(request): return JsonResponse({'type': 'error', 'object': 'login', 'category': 'password'}) # 将用户数据保存在 session 中,即实现了登录动作 login(request, user) - userdata = {"id": user.id, "username": user.username, "realname": user.realname, "is_banned": user.is_banned, "is_staff": user.is_staff} if 'user_id' in data and data['user_id'] != str(user.id): # 检测到小号 logger.warning(f'{data["user_id"][:50]} is different from {str(user.id)}.') - return JsonResponse({'type': 'success', 'user': userdata}) + return JsonResponse({'type': 'success', 'user': user_metadata(user)}) @require_GET @@ -83,8 +82,7 @@ def user_retrieve(request): login(request, user) logger.info(f'用户 {user.username}#{user.id} 邮箱找回密码') EmailVerifyRecord.objects.filter(hashkey=emailHashkey).delete() - userdata = {"id": user.id, "username": user.username, "realname": user.realname, "is_banned": user.is_banned, "is_staff": user.is_staff} - return JsonResponse({'type': 'success', 'user': userdata}) + return JsonResponse({'type': 'success', 'user': user_metadata(user)}) # 用户注册 @@ -113,10 +111,7 @@ def user_register(request): logger.info(f'用户 {new_user.username}#{new_user.id} 注册') # 顺手把过期的验证码删了 EmailVerifyRecord.objects.filter(hashkey=emailHashkey).delete() - return JsonResponse({'type': 'success', 'user': { - "id": new_user.id, "username": new_user.username, - "realname": new_user.realname, "is_banned": new_user.is_banned, "is_staff": new_user.is_staff} - }) + return JsonResponse({'type': 'success', 'user': user_metadata(new_user)}) else: return JsonResponse({'type': 'error', 'object': 'emailcode'}) else: @@ -134,7 +129,6 @@ def user_register(request): def check_collision(request): user = None if request.GET.get('username'): - print(request.GET.get('username')) user = UserProfile.objects.filter(username=request.GET.get('username')).first() elif request.GET.get('email'): user = UserProfile.objects.filter(email=request.GET.get('email')).first() diff --git a/front_end/src/App.vue b/front_end/src/App.vue index d2b2dc5..bcdfc32 100644 --- a/front_end/src/App.vue +++ b/front_end/src/App.vue @@ -16,7 +16,7 @@ - + diff --git a/front_end/src/components/Login.vue b/front_end/src/components/Login.vue index d11e6c3..4639b1b 100644 --- a/front_end/src/components/Login.vue +++ b/front_end/src/components/Login.vue @@ -101,7 +101,11 @@ const logout = async () => { realname: "", is_banned: false, is_staff: false, - country: "" + country: "", + accountlink: [], + identifiers: [], + videos: [], + loading: true, }; emit('logout'); // 向父组件发送消息 ElMessage.success({ message: t.t('common.msg.logoutSuccess'), offset: 68 }); diff --git a/front_end/src/components/dialogs/RegisterDialog.vue b/front_end/src/components/dialogs/RegisterDialog.vue index afd8b29..bdc72e1 100644 --- a/front_end/src/components/dialogs/RegisterDialog.vue +++ b/front_end/src/components/dialogs/RegisterDialog.vue @@ -18,7 +18,7 @@ {{ $t('login.agreeTAC1') - }} + }} {{ $t('login.agreeTAC2') }} diff --git a/front_end/src/components/widgets/CopyToClipboard.ts b/front_end/src/components/widgets/CopyToClipboard.ts new file mode 100644 index 0000000..5a90b58 --- /dev/null +++ b/front_end/src/components/widgets/CopyToClipboard.ts @@ -0,0 +1,22 @@ +import { ElNotification } from "element-plus"; +import { local } from "@/store"; +import i18n from "@/i18n"; +// @ts-ignore +const { t } = i18n.global; + +export const copyToClipboard = async (str: string) => { + try { + await navigator.clipboard.writeText(str); + ElNotification({ + title: t('msg.copyToClipboardSuccess'), + type: 'success', + duration: local.notification_duration, + }); + } catch(err) { + ElNotification({ + title: t('msg.copyToClipboardFail'), + type: 'error', + duration: local.notification_duration, + }) + } +} diff --git a/front_end/src/components/widgets/IdentifierManager.vue b/front_end/src/components/widgets/IdentifierManager.vue index e1967f2..91bb607 100644 --- a/front_end/src/components/widgets/IdentifierManager.vue +++ b/front_end/src/components/widgets/IdentifierManager.vue @@ -1,37 +1,57 @@ + + \ No newline at end of file diff --git a/front_end/src/components/widgets/UserArbiterCSV.vue b/front_end/src/components/widgets/UserArbiterCSV.vue new file mode 100644 index 0000000..4e52a44 --- /dev/null +++ b/front_end/src/components/widgets/UserArbiterCSV.vue @@ -0,0 +1,136 @@ + + + \ No newline at end of file diff --git a/front_end/src/i18n/locales/en.ts b/front_end/src/i18n/locales/en.ts index 7a6081d..d790b06 100644 --- a/front_end/src/i18n/locales/en.ts +++ b/front_end/src/i18n/locales/en.ts @@ -196,6 +196,8 @@ export default { captchaRequired: 'Captcha required', confirmPasswordMismatch: 'Mismatches password', connectionFail: 'Connection failed. Please try again', + copyToClipboardFail: 'Failed to copy.', + copyToClipboardSuccess: 'Text copied.', emailCodeInvalid: 'Email code is invalid or expired', emailCodeRequired: 'Email code required', emailCollision: 'Email already exists', @@ -249,8 +251,10 @@ export default { modeRecord: ' mode record: ' }, videos: 'All videos', - exportArbiterCSV: 'Export as stats_csv.csv', - exportArbiterCSVTooltip: 'Compatible with the datasheet generated by Minesweeper Arbiter.
Not supporting Leff, Reff, Deff, GZiNi and HZiNi at present.', + exportJSON: 'Export as JSON', + exportJSONTooltip: 'Raw data fetched from the server.', + exportArbiterCSV: 'Export as CSV', + exportArbiterCSVTooltip: 'Compatible with stats_csv.csv generated by Minesweeper Arbiter.
Not supporting Leff, Reff, Deff, GZiNi and HZiNi at present.', upload: { title: 'Video Upload', dragOrClick: `Drag files here or click here to select`, diff --git a/front_end/src/i18n/locales/zh-cn.ts b/front_end/src/i18n/locales/zh-cn.ts index 1ba127e..5b53e00 100644 --- a/front_end/src/i18n/locales/zh-cn.ts +++ b/front_end/src/i18n/locales/zh-cn.ts @@ -196,6 +196,8 @@ export default { captchaRequired: '请输入图形验证码', confirmPasswordMismatch: '密码和确认密码不一致', connectionFail: '无法连接到服务器,请重试', + copyToClipboardFail: '复制失败', + copyToClipboardSuccess: '复制成功', emailCodeInvalid: '邮箱验证码过期或不正确', emailCodeRequired: '请输入邮箱验证码', emailCollision: '邮箱已存在', @@ -249,8 +251,10 @@ export default { modeRecord: '模式纪录:' }, videos: '全部录像', - exportArbiterCSV: '导出stats_csv.csv', - exportArbiterCSVTooltip: '兼容 Minesweeper Arbiter 生成的数据表。
目前不支持 Leff, Reff, Deff, GZiNi, HZiNi。', + exportJSON: '导出JSON', + exportJSONTooltip: '从服务器获取的源数据', + exportArbiterCSV: '导出CSV', + exportArbiterCSVTooltip: '兼容 Minesweeper Arbiter 生成的 stats_csv.csv
目前不支持 Leff, Reff, Deff, GZiNi, HZiNi。', upload: { title: '上传录像', dragOrClick: `将录像拉到此处或 点击此处选择`, diff --git a/front_end/src/store/index.ts b/front_end/src/store/index.ts index 91183b9..531a409 100644 --- a/front_end/src/store/index.ts +++ b/front_end/src/store/index.ts @@ -11,7 +11,11 @@ export const store = defineStore('user', { realname: "", is_banned: false, is_staff: false, - country: "" + country: "", + accountlink: [], + identifiers: [], + videos: [], + loading: true, }, // 真正的用户 // 访问谁的地盘不再具有记忆性。即点“我的地盘”,将永远是“我”的地盘 // 想要访问特定用户,可以用url @@ -22,7 +26,12 @@ export const store = defineStore('user', { username: "", realname: "", is_banned: false, - country: "" + is_staff: false, + country: "", + accountlink: [] as any[], + identifiers: [] as string[], + videos: [] as any[], + loading: true, }, login_status: LoginStatus.Undefined, // 登录状态,全局维护 new_identifier: false, // 是否有新标识录像 diff --git a/front_end/src/views/PlayerProfileView.vue b/front_end/src/views/PlayerProfileView.vue index 05957a0..6a0f78e 100644 --- a/front_end/src/views/PlayerProfileView.vue +++ b/front_end/src/views/PlayerProfileView.vue @@ -1,8 +1,16 @@ \ No newline at end of file diff --git a/front_end/src/views/PlayerVideosView.vue b/front_end/src/views/PlayerVideosView.vue index 350a1ba..6913cc9 100644 --- a/front_end/src/views/PlayerVideosView.vue +++ b/front_end/src/views/PlayerVideosView.vue @@ -1,111 +1,15 @@ - - - diff --git a/front_end/src/views/PlayerView.vue b/front_end/src/views/PlayerView.vue index 963b3b3..34d2005 100644 --- a/front_end/src/views/PlayerView.vue +++ b/front_end/src/views/PlayerView.vue @@ -46,14 +46,13 @@
- {{ realname }}
+ {{ realname }} +
{{ signature }}
+ $t('profile.change') }} -
{{ $t('profile.identifier') }}{{ - identifiers.join(", ") }}
@@ -70,7 +69,7 @@ - + @@ -106,15 +105,12 @@ import { useRoute } from 'vue-router'; const t = useI18n(); const route = useRoute() -const loading = ref(true) - //编辑前的 const userid = ref(""); const username = ref(""); const realname = ref(""); const signature = ref(""); const popularity = ref(""); -const identifiers = ref([]); // 通过审核的标识 //编辑状态时的 const realname_edit = ref(""); @@ -164,7 +160,9 @@ function refresh() { store.player.username = data.username; store.player.is_banned = data.is_banned; store.player.country = data.country; - + store.player.is_staff = data.is_staff; + store.player.identifiers = data.identifiers; + store.player.videos = data.videos; userid.value = data.id; realname.value = data.realname; @@ -174,15 +172,13 @@ function refresh() { popularity.value = data.popularity; realname_edit.value = data.realname; signature_edit.value = data.signature; - identifiers.value.length = 0; - identifiers.value.push(...data.identifiers); // console.log(imageUrl); if (data.avatar) { imageUrl.value = "data:image/;base64," + data.avatar; imageUrlOld = "data:image/;base64," + data.avatar; } // console.log(imageUrl); - loading.value = false; + store.player.loading = false; }) } diff --git a/front_end/src/views/SettingView.vue b/front_end/src/views/SettingView.vue index c41dab0..4f0f370 100644 --- a/front_end/src/views/SettingView.vue +++ b/front_end/src/views/SettingView.vue @@ -38,21 +38,15 @@ {{ $t('common.toDo') }} {{ $t('common.toDo') }} - - - - - \ No newline at end of file