Fix Ranking code step1
This commit is contained in:
@ -27,6 +27,7 @@ from django.shortcuts import get_object_or_404
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import datetime, date
|
from datetime import datetime, date
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class LocationCatSerializer(serializers.ModelSerializer):
|
class LocationCatSerializer(serializers.ModelSerializer):
|
||||||
@ -876,3 +877,37 @@ class UserLastGoalTimeSerializer(serializers.Serializer):
|
|||||||
user_email = serializers.EmailField()
|
user_email = serializers.EmailField()
|
||||||
last_goal_time = serializers.DateTimeField()
|
last_goal_time = serializers.DateTimeField()
|
||||||
|
|
||||||
|
class LoginUserSerializer(serializers.Serializer):
|
||||||
|
identifier = serializers.CharField(required=True) # メールアドレスまたはゼッケン番号
|
||||||
|
password = serializers.CharField(required=True)
|
||||||
|
|
||||||
|
def validate(self, data):
|
||||||
|
identifier = data.get('identifier')
|
||||||
|
password = data.get('password')
|
||||||
|
|
||||||
|
if not identifier or not password:
|
||||||
|
raise serializers.ValidationError('認証情報を入力してください。')
|
||||||
|
|
||||||
|
# ゼッケン番号かメールアドレスかを判定
|
||||||
|
if '@' in identifier:
|
||||||
|
# メールアドレスの場合
|
||||||
|
user = authenticate(username=identifier, password=password)
|
||||||
|
else:
|
||||||
|
# ゼッケン番号の場合
|
||||||
|
try:
|
||||||
|
# ゼッケン番号からユーザーを検索
|
||||||
|
user = CustomUser.objects.filter(zekken_number=identifier).first()
|
||||||
|
if user:
|
||||||
|
# パスワード認証
|
||||||
|
if not user.check_password(password):
|
||||||
|
user = None
|
||||||
|
except ValueError:
|
||||||
|
user = None
|
||||||
|
|
||||||
|
if user and user.is_active:
|
||||||
|
return user
|
||||||
|
elif user and not user.is_active:
|
||||||
|
raise serializers.ValidationError('アカウントが有効化されていません。')
|
||||||
|
else:
|
||||||
|
raise serializers.ValidationError('認証情報が正しくありません。')
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
from sys import prefix
|
from sys import prefix
|
||||||
from rest_framework import urlpatterns
|
from rest_framework import urlpatterns
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView,RegistrationView,TempUserRegistrationView,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView, PasswordResetRequestView, PasswordResetConfirmView, NewCategoryViewSet,LocationInBound2,UserLastGoalTimeView,TeamEntriesView,update_entry_status,get_events,get_zekken_numbers,get_team_info,get_checkins,update_checkins,export_excel,debug_urls
|
from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView,RegistrationView,TempUserRegistrationView,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView, PasswordResetRequestView, PasswordResetConfirmView, NewCategoryViewSet,LocationInBound2,UserLastGoalTimeView,TeamEntriesView,update_entry_status,get_events,get_zekken_numbers,get_team_info,get_checkins,update_checkins,export_excel,debug_urls,get_ranking, all_ranking_top3
|
||||||
|
|
||||||
|
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
@ -128,6 +128,8 @@ urlpatterns += [
|
|||||||
path('get-goalimage/', views.get_goalimage, name='get-goalimage'),
|
path('get-goalimage/', views.get_goalimage, name='get-goalimage'),
|
||||||
|
|
||||||
path('get-photolist/', views.get_photo_list, name='get-photolist'),
|
path('get-photolist/', views.get_photo_list, name='get-photolist'),
|
||||||
|
path('api/rankings/<str:event_code>/<str:category_name>/', get_ranking, name='get_ranking'),
|
||||||
|
path('api/rankings/top3/<str:event_code>/', all_ranking_top3, name='all_ranking_top3'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
300
rog/views.py
300
rog/views.py
@ -23,7 +23,7 @@ import uuid
|
|||||||
from rest_framework.exceptions import ValidationError as DRFValidationError
|
from rest_framework.exceptions import ValidationError as DRFValidationError
|
||||||
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import F
|
from django.db.models import F,Sum
|
||||||
from rest_framework import viewsets, permissions, status
|
from rest_framework import viewsets, permissions, status
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
@ -853,6 +853,48 @@ class LoginAPI(generics.GenericAPIView):
|
|||||||
serializer_class = LoginUserSerializer
|
serializer_class = LoginUserSerializer
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
logger.info(f"Login attempt for identifier: {request.data.get('identifier', 'identifier not provided')}")
|
||||||
|
logger.debug(f"Request data: {request.data}")
|
||||||
|
|
||||||
|
serializer = self.get_serializer(data=request.data)
|
||||||
|
try:
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
user = serializer.validated_data
|
||||||
|
logger.info(f"User {user.email} logged in successfully")
|
||||||
|
|
||||||
|
# ユーザー情報をシリアライズ
|
||||||
|
user_data = UserSerializer(user, context=self.get_serializer_context()).data
|
||||||
|
|
||||||
|
# 認証トークンを生成
|
||||||
|
token = AuthToken.objects.create(user)[1]
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
"user": user_data,
|
||||||
|
"token": token
|
||||||
|
})
|
||||||
|
|
||||||
|
except serializers.ValidationError as e:
|
||||||
|
logger.error(f"Login failed for identifier {request.data.get('identifier', 'identifier not provided')}: {str(e)}")
|
||||||
|
logger.error(f"Serializer errors: {serializer.errors}")
|
||||||
|
|
||||||
|
error_msg = serializer.errors.get('non_field_errors', ['ログインに失敗しました。'])[0]
|
||||||
|
return Response({
|
||||||
|
"error": error_msg,
|
||||||
|
"details": serializer.errors
|
||||||
|
}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected error during login for identifier {request.data.get('identifier', 'identifier not provided')}: {str(e)}")
|
||||||
|
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
"error": "予期せぬエラーが発生しました。",
|
||||||
|
"details": str(e)
|
||||||
|
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def post_old(self, request, *args, **kwargs):
|
||||||
logger.info(f"Login attempt for user: {request.data.get('email', 'email not provided')}")
|
logger.info(f"Login attempt for user: {request.data.get('email', 'email not provided')}")
|
||||||
logger.debug(f"Request data: {request.data}")
|
logger.debug(f"Request data: {request.data}")
|
||||||
|
|
||||||
@ -3428,3 +3470,259 @@ def update_goal_time(request):
|
|||||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_team_status(last_checkin_time, goal_time, event_end_time):
|
||||||
|
"""
|
||||||
|
チームの状態を判定する
|
||||||
|
"""
|
||||||
|
now = timezone.now()
|
||||||
|
|
||||||
|
if goal_time:
|
||||||
|
return "ゴール"
|
||||||
|
|
||||||
|
if not last_checkin_time:
|
||||||
|
if now > event_end_time + timedelta(minutes=30):
|
||||||
|
return "棄権"
|
||||||
|
return "競技中"
|
||||||
|
|
||||||
|
# 最終チェックインから30分以上経過
|
||||||
|
if now > last_checkin_time + timedelta(minutes=30):
|
||||||
|
return "棄権"
|
||||||
|
|
||||||
|
return "競技中"
|
||||||
|
|
||||||
|
def calculate_late_points(goal_time, event_end_time):
|
||||||
|
"""遅刻による減点を計算する"""
|
||||||
|
if not goal_time or not event_end_time:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
minutes_late = max(0, int((goal_time - event_end_time).total_seconds() / 60))
|
||||||
|
return minutes_late * -50
|
||||||
|
|
||||||
|
def is_disqualified(start_time, goal_time, duration):
|
||||||
|
"""失格判定を行う"""
|
||||||
|
if not goal_time or not start_time or not duration:
|
||||||
|
return False # ゴール時間がない場合は失格としない(競技中の可能性)
|
||||||
|
|
||||||
|
# duration(timedelta)に15分を加算
|
||||||
|
max_time = start_time + duration + timedelta(minutes=15)
|
||||||
|
return goal_time > max_time
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
def get_ranking(request, event_code, category_name):
|
||||||
|
"""特定のイベントとクラスのランキングを取得する"""
|
||||||
|
try:
|
||||||
|
# イベントの情報を取得
|
||||||
|
event = NewEvent2.objects.get(event_name=event_code)
|
||||||
|
|
||||||
|
# 有効なエントリーを取得
|
||||||
|
entries = Entry.objects.filter(
|
||||||
|
event=event,
|
||||||
|
category__category_name=category_name,
|
||||||
|
is_active=True
|
||||||
|
).select_related('team', 'category')
|
||||||
|
|
||||||
|
rankings = []
|
||||||
|
disqualified = [] # 失格チームのリスト
|
||||||
|
|
||||||
|
for entry in entries:
|
||||||
|
# チェックインポイントを集計
|
||||||
|
checkins = GpsCheckin.objects.filter(
|
||||||
|
zekken_number=entry.zekken_number,
|
||||||
|
event_code=event_code
|
||||||
|
).aggregate(
|
||||||
|
total_points=Sum('points')
|
||||||
|
)
|
||||||
|
|
||||||
|
# 最後のチェックイン時刻を取得
|
||||||
|
last_checkin = GpsCheckin.objects.filter(
|
||||||
|
zekken_number=entry.zekken_number,
|
||||||
|
event_code=event_code
|
||||||
|
).order_by('-create_at').first()
|
||||||
|
|
||||||
|
last_checkin_time = last_checkin.create_at if last_checkin else None
|
||||||
|
|
||||||
|
# ゴール時間を取得 (最も早いゴール時間)
|
||||||
|
goal_record = GoalImages.objects.filter(
|
||||||
|
zekken_number=entry.zekken_number,
|
||||||
|
event_code=event_code
|
||||||
|
).order_by('goaltime').first()
|
||||||
|
|
||||||
|
goal_time = goal_record.goaltime if goal_record else None
|
||||||
|
total_points = checkins['total_points'] or 0
|
||||||
|
|
||||||
|
# イベントの終了予定時刻を計算
|
||||||
|
expected_end_time = event.start_datetime + entry.category.duration
|
||||||
|
|
||||||
|
# チーム状態の判定
|
||||||
|
team_status = get_team_status(last_checkin_time, goal_time, expected_end_time)
|
||||||
|
|
||||||
|
# 失格判定
|
||||||
|
if is_disqualified(event.start_datetime, goal_time, entry.category.duration):
|
||||||
|
disqualified.append({
|
||||||
|
'team_name': entry.team.team_name,
|
||||||
|
'zekken_number': entry.zekken_number,
|
||||||
|
'point': total_points,
|
||||||
|
'late_point': 0,
|
||||||
|
'goal_time': goal_time,
|
||||||
|
'reason': '制限時間超過',
|
||||||
|
'status': team_status
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 遅刻減点を計算
|
||||||
|
late_points = calculate_late_points(goal_time, expected_end_time)
|
||||||
|
|
||||||
|
rankings.append({
|
||||||
|
'team_name': entry.team.team_name,
|
||||||
|
'zekken_number': entry.zekken_number,
|
||||||
|
'point': total_points,
|
||||||
|
'late_point': abs(late_points),
|
||||||
|
'final_point': total_points + late_points,
|
||||||
|
'goal_time': goal_time,
|
||||||
|
'status': team_status,
|
||||||
|
'last_checkin': last_checkin_time
|
||||||
|
})
|
||||||
|
|
||||||
|
# ポイントの高い順(同点の場合はゴール時間が早い順)にソート
|
||||||
|
# 棄権チームを最後に
|
||||||
|
rankings.sort(key=lambda x: (
|
||||||
|
-1 if x['status'] != '棄権' else 0, # 棄権でないチームを優先
|
||||||
|
-x['final_point'], # 得点の高い順
|
||||||
|
x['goal_time'] or datetime.max # ゴール時間の早い順
|
||||||
|
))
|
||||||
|
|
||||||
|
# 有効なランキングと失格チームを結合
|
||||||
|
final_rankings = {
|
||||||
|
'rankings': rankings,
|
||||||
|
'disqualified': disqualified
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(final_rankings)
|
||||||
|
|
||||||
|
except NewEvent2.DoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{"error": "Specified event not found"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in get_ranking: {str(e)}", exc_info=True)
|
||||||
|
return Response(
|
||||||
|
{"error": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
@api_view(['GET'])
|
||||||
|
def all_ranking_top3(request, event_code):
|
||||||
|
"""特定のイベントの全クラスのTOP3を取得する"""
|
||||||
|
try:
|
||||||
|
# イベントの情報を取得
|
||||||
|
event = NewEvent2.objects.get(event_name=event_code)
|
||||||
|
|
||||||
|
# イベントの有効なカテゴリーを取得
|
||||||
|
categories = NewCategory.objects.filter(
|
||||||
|
entry__event=event,
|
||||||
|
entry__is_active=True
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
rankings = {}
|
||||||
|
for category in categories:
|
||||||
|
# カテゴリーごとのエントリーを取得
|
||||||
|
entries = Entry.objects.filter(
|
||||||
|
event=event,
|
||||||
|
category=category,
|
||||||
|
is_active=True
|
||||||
|
).select_related('team')
|
||||||
|
|
||||||
|
category_rankings = []
|
||||||
|
disqualified = [] # カテゴリーごとの失格チーム
|
||||||
|
|
||||||
|
for entry in entries:
|
||||||
|
# チェックインポイントを集計
|
||||||
|
checkins = GpsCheckin.objects.filter(
|
||||||
|
zekken_number=entry.zekken_number,
|
||||||
|
event_code=event_code
|
||||||
|
).aggregate(
|
||||||
|
total_points=Sum('points')
|
||||||
|
)
|
||||||
|
|
||||||
|
# 最後のチェックイン時刻を取得
|
||||||
|
last_checkin = GpsCheckin.objects.filter(
|
||||||
|
zekken_number=entry.zekken_number,
|
||||||
|
event_code=event_code
|
||||||
|
).order_by('-create_at').first()
|
||||||
|
|
||||||
|
last_checkin_time = last_checkin.create_at if last_checkin else None
|
||||||
|
|
||||||
|
# ゴール時間を取得
|
||||||
|
goal_record = GoalImages.objects.filter(
|
||||||
|
zekken_number=entry.zekken_number,
|
||||||
|
event_code=event_code
|
||||||
|
).order_by('goaltime').first()
|
||||||
|
|
||||||
|
goal_time = goal_record.goaltime if goal_record else None
|
||||||
|
total_points = checkins['total_points'] or 0
|
||||||
|
|
||||||
|
# イベントの終了予定時刻を計算
|
||||||
|
expected_end_time = event.start_datetime + category.duration
|
||||||
|
|
||||||
|
# チーム状態の判定
|
||||||
|
team_status = get_team_status(last_checkin_time, goal_time, expected_end_time)
|
||||||
|
|
||||||
|
# 失格判定
|
||||||
|
if is_disqualified(event.start_datetime, goal_time, entry.category.duration):
|
||||||
|
disqualified.append({
|
||||||
|
'team_name': entry.team.team_name,
|
||||||
|
'zekken_number': entry.zekken_number,
|
||||||
|
'point': total_points,
|
||||||
|
'late_point': 0,
|
||||||
|
'goal_time': goal_time,
|
||||||
|
'reason': '制限時間超過',
|
||||||
|
'status': team_status
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 遅刻減点を計算
|
||||||
|
late_points = calculate_late_points(goal_time, expected_end_time)
|
||||||
|
|
||||||
|
category_rankings.append({
|
||||||
|
'team_name': entry.team.team_name,
|
||||||
|
'zekken_number': entry.zekken_number,
|
||||||
|
'point': total_points,
|
||||||
|
'late_point': abs(late_points),
|
||||||
|
'final_point': total_points + late_points,
|
||||||
|
'goal_time': goal_time,
|
||||||
|
'status': team_status,
|
||||||
|
'last_checkin': last_checkin_time
|
||||||
|
})
|
||||||
|
|
||||||
|
# ポイントの高い順(同点の場合はゴール時間が早い順)にソート
|
||||||
|
# 棄権チームを最後に
|
||||||
|
category_rankings.sort(key=lambda x: (
|
||||||
|
-1 if x['status'] != '棄権' else 0, # 棄権でないチームを優先
|
||||||
|
-x['final_point'], # 得点の高い順
|
||||||
|
x['goal_time'] or datetime.max # ゴール時間の早い順
|
||||||
|
))
|
||||||
|
|
||||||
|
# TOP3のみを保持(棄権を除く)
|
||||||
|
top_rankings = [r for r in category_rankings if r['status'] != '棄権'][:3]
|
||||||
|
|
||||||
|
rankings[category.category_name] = {
|
||||||
|
'rankings': top_rankings,
|
||||||
|
'disqualified': disqualified
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response(rankings)
|
||||||
|
|
||||||
|
except NewEvent2.DoesNotExist:
|
||||||
|
return Response(
|
||||||
|
{"error": "Specified event not found"},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in all_ranking_top3: {str(e)}", exc_info=True)
|
||||||
|
return Response(
|
||||||
|
{"error": str(e)},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
@ -10,9 +10,40 @@
|
|||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" rel="stylesheet">
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50">
|
<body class="bg-gray-50">
|
||||||
<div class="container mx-auto p-4">
|
<!-- ログインフォーム -->
|
||||||
<div class="bg-white rounded-lg shadow-lg p-6 mb-6">
|
<div id="loginForm" class="fixed inset-0 bg-gray-800 bg-opacity-50 flex items-center justify-center">
|
||||||
|
<div class="bg-white p-8 rounded-lg shadow-lg w-96">
|
||||||
|
<h2 class="text-2xl font-bold mb-6 text-center">ログイン</h2>
|
||||||
|
<form onsubmit="return login(event)">
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block text-gray-700 text-sm font-bold mb-2" for="username">
|
||||||
|
ユーザー名
|
||||||
|
</label>
|
||||||
|
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
|
||||||
|
id="username" type="text" placeholder="ユーザー名">
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label class="block text-gray-700 text-sm font-bold mb-2" for="password">
|
||||||
|
パスワード
|
||||||
|
</label>
|
||||||
|
<input class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 mb-3 leading-tight focus:outline-none focus:shadow-outline"
|
||||||
|
id="password" type="password" placeholder="パスワード">
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline"
|
||||||
|
type="submit">
|
||||||
|
ログイン
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="mainContent" class="container mx-auto p-4">
|
||||||
|
<div class="bg-white rounded-lg shadow-lg p-6 mb-6" style="display: none;">
|
||||||
<h1 class="text-2xl font-bold mb-6">通過審査管理画面</h1>
|
<h1 class="text-2xl font-bold mb-6">通過審査管理画面</h1>
|
||||||
|
<button onclick="logout()" class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700">
|
||||||
|
ログアウト
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- 選択フォーム -->
|
<!-- 選択フォーム -->
|
||||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||||||
@ -125,8 +156,62 @@
|
|||||||
let original_goal_time = '';
|
let original_goal_time = '';
|
||||||
let selected_event_code = '';
|
let selected_event_code = '';
|
||||||
|
|
||||||
|
// ユーザー認証用の定数(実際の運用ではサーバーサイドで管理すべき)
|
||||||
|
const VALID_USERNAME = 'admin';
|
||||||
|
const VALID_PASSWORD = 'password123';
|
||||||
|
|
||||||
|
// セッション管理
|
||||||
|
function checkAuth() {
|
||||||
|
const isAuthenticated = sessionStorage.getItem('isAuthenticated');
|
||||||
|
if (isAuthenticated) {
|
||||||
|
document.getElementById('loginForm').style.display = 'none';
|
||||||
|
document.getElementById('mainContent').style.display = 'block';
|
||||||
|
} else {
|
||||||
|
document.getElementById('loginForm').style.display = 'flex';
|
||||||
|
document.getElementById('mainContent').style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ログイン処理
|
||||||
|
async function login(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const username = document.getElementById('username').value;
|
||||||
|
const password = document.getElementById('password').value;
|
||||||
|
|
||||||
|
const response = await fetch('/api/login/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
identifier: username, // メールアドレス
|
||||||
|
password: password
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.error || 'ログインに失敗しました');
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuperVisor User のみを許可する。
|
||||||
|
console.info('Login successful:', data);
|
||||||
|
sessionStorage.setItem('isAuthenticated', 'true');
|
||||||
|
checkAuth();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ログアウト処理
|
||||||
|
function logout() {
|
||||||
|
sessionStorage.removeItem('isAuthenticated');
|
||||||
|
checkAuth();
|
||||||
|
}
|
||||||
|
|
||||||
// イベントリスナーの設定
|
// イベントリスナーの設定
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
checkAuth();
|
||||||
|
|
||||||
// Sortable初期化これで、通過順序を変更できる
|
// Sortable初期化これで、通過順序を変更できる
|
||||||
const checkinList = document.getElementById('checkinList');
|
const checkinList = document.getElementById('checkinList');
|
||||||
new Sortable(checkinList, {
|
new Sortable(checkinList, {
|
||||||
|
|||||||
295
supervisor/html/ranking.html
Normal file
295
supervisor/html/ranking.html
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>ランキング</title>
|
||||||
|
<style>
|
||||||
|
.box2 { margin: 10px; padding: 10px; border: 1px solid #ccc; }
|
||||||
|
.best3 { margin: 5px 0; padding: 5px; }
|
||||||
|
.span2 { margin-left: 20px; }
|
||||||
|
.span3 { font-weight: bold; }
|
||||||
|
.span6 { display: inline-block; width: 30px; }
|
||||||
|
.black { background-color: #f0f0f0; padding: 10px; }
|
||||||
|
.arrow { margin: 10px 0; }
|
||||||
|
.arrow2 { margin: 10px 0; }
|
||||||
|
.disqualified { color: #999; }
|
||||||
|
.status {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
.status-retired {
|
||||||
|
background-color: #ffebee;
|
||||||
|
color: #c62828;
|
||||||
|
}
|
||||||
|
.status-finished {
|
||||||
|
background-color: #e8f5e9;
|
||||||
|
color: #2e7d32;
|
||||||
|
}
|
||||||
|
.status-running {
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
color: #1565c0;
|
||||||
|
}
|
||||||
|
.disqualified-header {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 5px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-left: 4px solid #999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="ranking">
|
||||||
|
<div class="black">
|
||||||
|
<span class="span3"></span>
|
||||||
|
<span style="font-size: 24px">ランキング</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="arrow">
|
||||||
|
<select id="eventSelect">
|
||||||
|
<option value="">イベントを選択してください</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="arrow2">
|
||||||
|
<select id="classSelect" disabled>
|
||||||
|
<option value="">クラスを選択してください</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="toggleButton" onclick="toggleView()">TOP3表示</button>
|
||||||
|
|
||||||
|
<!-- 通常のランキング表示 -->
|
||||||
|
<div id="normalRanking">
|
||||||
|
<div id="teamList"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- TOP3のランキング表示 -->
|
||||||
|
<div id="top3Ranking" style="display: none;">
|
||||||
|
<div id="top3List"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let showTop3 = false;
|
||||||
|
|
||||||
|
// ページ読み込み時にイベント一覧を取得
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
await loadEvents();
|
||||||
|
setupEventListeners();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 日時のフォーマット
|
||||||
|
function formatDateTime(dateStr) {
|
||||||
|
if (!dateStr) return '未ゴール';
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return date.toLocaleString('ja-JP', {
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// イベントリスナーの設定
|
||||||
|
function setupEventListeners() {
|
||||||
|
document.getElementById('eventSelect').addEventListener('change', async function() {
|
||||||
|
const eventCode = this.value;
|
||||||
|
if (eventCode) {
|
||||||
|
await loadClasses(eventCode);
|
||||||
|
await updateRankings();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('classSelect').addEventListener('change', async function() {
|
||||||
|
await updateRankings();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// イベント一覧の取得と表示
|
||||||
|
async function loadEvents() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/newevent2/');
|
||||||
|
const events = await response.json();
|
||||||
|
const select = document.getElementById('eventSelect');
|
||||||
|
|
||||||
|
events.filter(event => event.public).forEach(event => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = event.event_name;
|
||||||
|
option.textContent = event.event_name;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('イベント一覧の取得に失敗:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// クラス一覧の取得と表示
|
||||||
|
async function loadClasses(eventCode) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/categories/${eventCode}/`);
|
||||||
|
const classes = await response.json();
|
||||||
|
const select = document.getElementById('classSelect');
|
||||||
|
|
||||||
|
// 既存のオプションをクリア
|
||||||
|
select.innerHTML = '<option value="">クラスを選択してください</option>';
|
||||||
|
select.disabled = false;
|
||||||
|
|
||||||
|
classes.forEach(cls => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = cls.category_name;
|
||||||
|
option.textContent = cls.category_name;
|
||||||
|
select.appendChild(option);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('クラス一覧の取得に失敗:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ランキングの更新
|
||||||
|
async function updateRankings() {
|
||||||
|
const eventCode = document.getElementById('eventSelect').value;
|
||||||
|
if (!eventCode) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (showTop3) {
|
||||||
|
await loadTop3Rankings(eventCode);
|
||||||
|
} else {
|
||||||
|
const classCode = document.getElementById('classSelect').value;
|
||||||
|
if (classCode) {
|
||||||
|
await loadClassRankings(eventCode, classCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('ランキングの取得に失敗:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// クラス別ランキングの表示
|
||||||
|
async function loadClassRankings(eventCode, classCode) {
|
||||||
|
const response = await fetch(`/api/rankings/${eventCode}/${classCode}/`);
|
||||||
|
const rankingData = await response.json();
|
||||||
|
displayNormalRankings(rankingData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TOP3ランキングの表示
|
||||||
|
async function loadTop3Rankings(eventCode) {
|
||||||
|
const response = await fetch(`/api/rankings/top3/${eventCode}/`);
|
||||||
|
const rankingData = await response.json();
|
||||||
|
displayTop3Rankings(rankingData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通常ランキングの表示処理
|
||||||
|
function displayNormalRankings(rankingData) {
|
||||||
|
const container = document.getElementById('teamList');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
// 有効なランキングの表示
|
||||||
|
rankingData.rankings.forEach((team, index) => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'best';
|
||||||
|
const statusClass = team.status === '棄権' ? 'status-retired' :
|
||||||
|
team.status === 'ゴール' ? 'status-finished' : 'status-running';
|
||||||
|
div.innerHTML = `
|
||||||
|
${index + 1}. ${team.team_name} (${team.zekken_number})
|
||||||
|
<span class="span2">合計得点:${team.final_point}</span>
|
||||||
|
<span class="span2">獲得ポイント:${team.point}</span>
|
||||||
|
<span class="span2">遅刻減点: ${team.late_point}</span>
|
||||||
|
<span class="span2">最終更新: ${formatDateTime(team.last_checkin)}</span>
|
||||||
|
<span class="status ${statusClass}">(${team.status})</span>
|
||||||
|
`;
|
||||||
|
container.appendChild(div);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 失格チームの表示(見出し)
|
||||||
|
if (rankingData.disqualified && rankingData.disqualified.length > 0) {
|
||||||
|
const disqHeader = document.createElement('div');
|
||||||
|
disqHeader.className = 'disqualified-header';
|
||||||
|
disqHeader.innerHTML = '<h3>失格チーム</h3>';
|
||||||
|
container.appendChild(disqHeader);
|
||||||
|
|
||||||
|
// 失格チームのリスト
|
||||||
|
rankingData.disqualified.forEach(team => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'best disqualified';
|
||||||
|
div.innerHTML = `
|
||||||
|
${team.team_name} (${team.zekken_number})
|
||||||
|
<span class="span2">獲得ポイント:${team.point}</span>
|
||||||
|
<span class="span2">理由: ${team.reason}</span>
|
||||||
|
<span class="span2">ゴール: ${formatDateTime(team.goal_time)}</span>
|
||||||
|
`;
|
||||||
|
container.appendChild(div);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TOP3ランキングの表示処理
|
||||||
|
function displayTop3Rankings(rankingData) {
|
||||||
|
const container = document.getElementById('top3List');
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
Object.entries(rankingData).forEach(([category, data]) => {
|
||||||
|
const categoryDiv = document.createElement('div');
|
||||||
|
categoryDiv.className = 'box2';
|
||||||
|
|
||||||
|
const categoryHeader = document.createElement('h3');
|
||||||
|
categoryHeader.textContent = category;
|
||||||
|
categoryDiv.appendChild(categoryHeader);
|
||||||
|
|
||||||
|
// 有効なランキングの表示
|
||||||
|
data.rankings.forEach((team, index) => {
|
||||||
|
const teamDiv = document.createElement('div');
|
||||||
|
teamDiv.className = 'best3';
|
||||||
|
const statusClass = team.status === '棄権' ? 'status-retired' :
|
||||||
|
team.status === 'ゴール' ? 'status-finished' : 'status-running';
|
||||||
|
teamDiv.innerHTML = `
|
||||||
|
<span class="span6">${index + 1}</span>
|
||||||
|
${team.team_name} (${team.zekken_number}) <span class="status ${statusClass}">(${team.status})</span><br>
|
||||||
|
合計得点:${team.final_point} (獲得:${team.point} 減点:${team.late_point})<br>
|
||||||
|
最終更新: ${formatDateTime(team.last_checkin)}
|
||||||
|
`;
|
||||||
|
categoryDiv.appendChild(teamDiv);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 失格チームの表示
|
||||||
|
if (data.disqualified && data.disqualified.length > 0) {
|
||||||
|
const disqHeader = document.createElement('div');
|
||||||
|
disqHeader.className = 'disqualified-header';
|
||||||
|
disqHeader.innerHTML = '<h4>失格チーム</h4>';
|
||||||
|
categoryDiv.appendChild(disqHeader);
|
||||||
|
|
||||||
|
data.disqualified.forEach(team => {
|
||||||
|
const teamDiv = document.createElement('div');
|
||||||
|
teamDiv.className = 'best3 disqualified';
|
||||||
|
teamDiv.innerHTML = `
|
||||||
|
${team.team_name} (${team.zekken_number})<br>
|
||||||
|
獲得ポイント:${team.point} 理由:${team.reason}<br>
|
||||||
|
ゴール: ${formatDateTime(team.goal_time)}
|
||||||
|
`;
|
||||||
|
categoryDiv.appendChild(teamDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(categoryDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表示モードの切り替え
|
||||||
|
function toggleView() {
|
||||||
|
showTop3 = !showTop3;
|
||||||
|
const button = document.getElementById('toggleButton');
|
||||||
|
const normalRanking = document.getElementById('normalRanking');
|
||||||
|
const top3Ranking = document.getElementById('top3Ranking');
|
||||||
|
const classSelect = document.getElementById('classSelect');
|
||||||
|
|
||||||
|
button.textContent = showTop3 ? 'クラス別ランキング' : 'TOP3表示';
|
||||||
|
normalRanking.style.display = showTop3 ? 'none' : 'block';
|
||||||
|
top3Ranking.style.display = showTop3 ? 'block' : 'none';
|
||||||
|
classSelect.disabled = showTop3;
|
||||||
|
|
||||||
|
updateRankings();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
486
supervisor/html/ranking_bck.html
Normal file
486
supervisor/html/ranking_bck.html
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<title>ランキング</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
|
||||||
|
.flex-slave {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#map {
|
||||||
|
width: 800px;
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#best{
|
||||||
|
position: relative;
|
||||||
|
line-height: 1.5em;
|
||||||
|
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 10px 25px 0 rgba(0, 0, 0, .5);
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius:8px;
|
||||||
|
counter-increment: count-ex1-5;
|
||||||
|
content: counter(number);
|
||||||
|
padding: 0px 10px 10px 30px;
|
||||||
|
margin:0px 0px 0px 5px;
|
||||||
|
}
|
||||||
|
#best::marker{
|
||||||
|
font-weight:bold;
|
||||||
|
color: #ff0801;
|
||||||
|
}
|
||||||
|
|
||||||
|
#best li{
|
||||||
|
counter-increment: count-ex1-5;
|
||||||
|
content: counter(number);
|
||||||
|
padding: 8px 10px 0px 0px;
|
||||||
|
margin:8px 0px 0px 8px;
|
||||||
|
line-height: 2em;
|
||||||
|
}
|
||||||
|
li::marker{
|
||||||
|
font-weight:bold;
|
||||||
|
color: #ff0801;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#best::before {
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
background: #ff0801;
|
||||||
|
color: #FFF;
|
||||||
|
font-size: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
left: 0;
|
||||||
|
width: 23px;
|
||||||
|
height: 23px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
top: 5px;
|
||||||
|
font-family: "Font Awesome 5 Free";
|
||||||
|
content: "\f521";
|
||||||
|
font-weight: 900;
|
||||||
|
margin:6px 0px 0px 10px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.black{
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
h5 {
|
||||||
|
position: relative;
|
||||||
|
padding: 0px 0px 0px 70px;
|
||||||
|
|
||||||
|
|
||||||
|
background-image: linear-gradient(0deg, #b8751e 0%, #ffce08 37%, #fefeb2 47%, #fafad6 50%, #fefeb2 53%, #e1ce08 63%, #b8751e 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
display:flex;
|
||||||
|
align-items: center;
|
||||||
|
height:60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 .span3 {
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
left: 0px;
|
||||||
|
display: inline-block;
|
||||||
|
width: 52px;
|
||||||
|
height: px;
|
||||||
|
text-align: center;
|
||||||
|
background: #fa4141;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 .span3:before,
|
||||||
|
h5 .span3:after {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 .span3:before {
|
||||||
|
right: -10px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-right: 10px solid transparent;
|
||||||
|
border-bottom: 10px solid #d90606;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 .span3:after {
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
display: block;
|
||||||
|
height: %;
|
||||||
|
border: 26px solid #fa4141;
|
||||||
|
border-bottom-width: 15px;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 .span3 i {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
color: #fff100;
|
||||||
|
padding-top: 10px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
.best
|
||||||
|
select.name{
|
||||||
|
display: block;
|
||||||
|
margin-bottom: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.best2 li{
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 1.5rem 2rem 1.5rem 130px;
|
||||||
|
word-break: break-all;
|
||||||
|
border-top: 3px solid #000;
|
||||||
|
border-radius: 12px 0 0 0;
|
||||||
|
margin: 10px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
.best2 span{
|
||||||
|
font-size: 40px;
|
||||||
|
font-size: 4rem;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
display: block;
|
||||||
|
padding: 3px 20px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 10px 0 20px 10px;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
.button3{
|
||||||
|
color: #fff;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
border-radius: 0;
|
||||||
|
background-image: -webkit-linear-gradient(left, #fa709a 0%, #fee140 100%);
|
||||||
|
background-image: linear-gradient(to right, #fa709a 0%, #fee140 100%);
|
||||||
|
-webkit-box-shadow: 0 5px 5px rgba(0, 0, 0, .1);
|
||||||
|
box-shadow: 0 3px 5px rgba(0, 0, 0, .1);
|
||||||
|
border-radius: 100vh;
|
||||||
|
font-family: "Arial", "メイリオ";
|
||||||
|
/*letter-spacing: 0.1em;*/
|
||||||
|
padding: 7px 25px 7px 25px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.button3:hover{
|
||||||
|
-webkit-transform: translate(0, -2px);
|
||||||
|
transform: translate(0, -2px);
|
||||||
|
color: #fff;
|
||||||
|
-webkit-box-shadow: 0 8px 15px rgba(0, 0, 0, .2);
|
||||||
|
box-shadow: 0 8px 15px rgba(0, 0, 0, .2);
|
||||||
|
font-family: "Arial", "メイリオ";
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#best::before {
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
background: #ff0801;
|
||||||
|
color: #FFF;
|
||||||
|
font-size: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
left: 0;
|
||||||
|
width: 23px;
|
||||||
|
height: 23px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
top: 5px;
|
||||||
|
font-family: "Font Awesome 5 Free";
|
||||||
|
content: "\f521";
|
||||||
|
font-weight: 900;
|
||||||
|
margin:6px 0px 0px 10px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
display: block;
|
||||||
|
border-left: solid 10px #27acd9;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-color: transparent transparent transparent blue;
|
||||||
|
font-weight: bold;
|
||||||
|
box-shadow: 3px 5px 3px -2px #aaaaaa,3px 3px 2px 0px #ffffff inset;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box2{
|
||||||
|
position: relative;
|
||||||
|
line-height: 1.5em;
|
||||||
|
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 10px 25px 0 rgba(0, 0, 0, .5);
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius:8px;
|
||||||
|
counter-increment: count-ex1-5;
|
||||||
|
content: counter(number);
|
||||||
|
padding: 10px 10px 10px 30px;
|
||||||
|
margin:10px 0px 0px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.best2::before {
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
background: #ff0801;
|
||||||
|
color: #FFF;
|
||||||
|
font-size: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
left: 0;
|
||||||
|
width: 23px;
|
||||||
|
height: 23px;
|
||||||
|
line-height: 22px;
|
||||||
|
text-align: center;
|
||||||
|
top: 5px;
|
||||||
|
font-family: "Font Awesome 5 Free";
|
||||||
|
content: "\f521";
|
||||||
|
font-weight: 900;
|
||||||
|
margin: 10px 0px 0px 10px;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
h3{
|
||||||
|
margin: 3px 0px 0px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
|
||||||
|
#best{
|
||||||
|
position: relative;
|
||||||
|
line-height: 1.5em;
|
||||||
|
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 10px 25px 0 rgba(0, 0, 0, .5);
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius:8px;
|
||||||
|
counter-increment: count-ex1-5;
|
||||||
|
content: counter(number);
|
||||||
|
padding: 0px 10px 10px 30px;
|
||||||
|
margin:0px 0px 0px 5px;
|
||||||
|
}
|
||||||
|
#best::marker{
|
||||||
|
font-weight:bold;
|
||||||
|
color: #ff0801;
|
||||||
|
}
|
||||||
|
|
||||||
|
#best li{
|
||||||
|
counter-increment: count-ex1-5;
|
||||||
|
content: counter(number);
|
||||||
|
padding: 8px 10px 0px 0px;
|
||||||
|
margin:8px 0px 0px 8px;
|
||||||
|
line-height: 2em;
|
||||||
|
margin: 10px 0px 0px 0px;
|
||||||
|
}
|
||||||
|
li::marker{
|
||||||
|
font-weight:bold;
|
||||||
|
color: #ff0801;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
|
}
|
||||||
|
select{
|
||||||
|
width:100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
.button3{
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
select.name{
|
||||||
|
margin-bottom: -20px;
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow{
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.arrow::after {
|
||||||
|
color: #828282;
|
||||||
|
position: absolute;
|
||||||
|
top:18px; /* 矢印の位置 */
|
||||||
|
right: 25px; /* 矢印の位置 */
|
||||||
|
width: 13px; /* 矢印の大きさ */
|
||||||
|
height: 13px; /* 矢印の大きさ */
|
||||||
|
border-top: 3px solid #58504A; /* 矢印の線 */
|
||||||
|
border-right: 3px solid #58504A; /* 矢印の線 */
|
||||||
|
-webkit-transform: rotate(135deg); /* 矢印の傾き */
|
||||||
|
transform: rotate(135deg); /* 矢印の傾き */
|
||||||
|
pointer-events: none; /* 矢印部分もクリック可能にする */
|
||||||
|
content: "";
|
||||||
|
border-color: #828282;
|
||||||
|
}
|
||||||
|
.arrow2{
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.arrow2::after {
|
||||||
|
color: #828282;
|
||||||
|
position: absolute;
|
||||||
|
top:20px; /* 矢印の位置 */
|
||||||
|
right: 25px; /* 矢印の位置 */
|
||||||
|
width: 13px; /* 矢印の大きさ */
|
||||||
|
height: 13px; /* 矢印の大きさ */
|
||||||
|
border-top: 3px solid #58504A; /* 矢印の線 */
|
||||||
|
border-right: 3px solid #58504A; /* 矢印の線 */
|
||||||
|
-webkit-transform: rotate(135deg); /* 矢印の傾き */
|
||||||
|
transform: rotate(135deg); /* 矢印の傾き */
|
||||||
|
pointer-events: none; /* 矢印部分もクリック可能にする */
|
||||||
|
content: "";
|
||||||
|
border-color: #828282;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="ranking">
|
||||||
|
<div class="black">
|
||||||
|
<h5><span class="span3"><i class="fas fa-crown"></i></span>
|
||||||
|
<span style="font-size: 24px">ランキング</span></h5>
|
||||||
|
</div>
|
||||||
|
<form @submit.prevent="ranking_view">
|
||||||
|
<div class="arrow">
|
||||||
|
<select class="name" v-model="selectedEvent">
|
||||||
|
<option disabled value="">イベント一覧</option>
|
||||||
|
<option selected value="FC岐阜">with FC岐阜</option>
|
||||||
|
<!--
|
||||||
|
<option value="関ケ原2410">関ケ原-2024年10月</option>
|
||||||
|
<option value="養老2410">養老-2024年10月</option>
|
||||||
|
<option value="大垣2410">大垣-2024年10月</option>
|
||||||
|
<option value="各務原2410">各務原-2024年10月</option>
|
||||||
|
<option value="多治見2410">多治見-2024年10月</option>
|
||||||
|
<option value="美濃加茂2410">美濃加茂-2024年10月</option>
|
||||||
|
<option value="下呂2410">下呂-2024年10月</option>
|
||||||
|
<option value="郡上2410">郡上-2024年10月</option>
|
||||||
|
<option value="高山2410">高山-2024年10月</option>
|
||||||
|
|
||||||
|
<option value="関ケ原2409">関ケ原-2024年9月</option>
|
||||||
|
|
||||||
|
<option value="養老2409">養老-2024年9月</option>
|
||||||
|
<option value="大垣2409">大垣-2024年9月</option>
|
||||||
|
<option value="各務原2409">各務原-2024年9月</option>
|
||||||
|
<option value="多治見2409">多治見-2024年9月</option>
|
||||||
|
<option value="美濃加茂2409">美濃加茂-2024年9月</option>
|
||||||
|
<option value="下呂2409">下呂-2024年9月</option>
|
||||||
|
<option value="郡上2409">郡上-2024年9月</option>
|
||||||
|
<option value="高山2409">高山-2024年9月</option>
|
||||||
|
|
||||||
|
-->
|
||||||
|
<option value="美濃加茂">岐阜ロゲin美濃加茂</option>
|
||||||
|
<option value="養老ロゲ">養老町</option>
|
||||||
|
<option value="岐阜市">岐阜市</option>
|
||||||
|
<option value="大垣2">岐阜ロゲin大垣@イオンモール大垣</option>
|
||||||
|
<option value="大垣">岐阜ロゲin大垣</option>
|
||||||
|
<option value="多治見">岐阜ロゲin多治見</option>
|
||||||
|
<option value="各務原">岐阜ロゲin各務原</option>
|
||||||
|
<option value="下呂">岐阜ロゲin下呂温泉</option>
|
||||||
|
<option value="郡上">岐阜ロゲin郡上</option>
|
||||||
|
<option value="高山">岐阜ロゲin高山</option>
|
||||||
|
</select>
|
||||||
|
<div class="arrow2">
|
||||||
|
<select v-model="selectedClass">
|
||||||
|
<option selected value="top3">top3</option>
|
||||||
|
<option value="3時間一般">3時間一般</option>
|
||||||
|
<option value="3時間ファミリー">3時間ファミリー</option>
|
||||||
|
<option value="3時間自転車">3時間自転車</option>
|
||||||
|
<option value="3時間ソロ男子">3時間ソロ男子</option>
|
||||||
|
<option value="3時間ソロ女子">3時間ソロ女子</option>
|
||||||
|
<option value="3時間パラロゲ">3時間パラロゲ</option>
|
||||||
|
<option value="5時間一般">5時間一般</option>
|
||||||
|
<option value="5時間ファミリー">5時間ファミリー</option>
|
||||||
|
<option value="5時間自転車">5時間自転車</option>
|
||||||
|
<option value="5時間ソロ男子">5時間ソロ男子</option>
|
||||||
|
<option value="5時間ソロ女子">5時間ソロ女子</option>
|
||||||
|
</div>
|
||||||
|
</select>
|
||||||
|
<button class="button3" type="submit">CLICK</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<ol v-if="top_three_flag == false" >
|
||||||
|
<div id="best" v-for="team in team_list">
|
||||||
|
<li>
|
||||||
|
{{ team.team_name }}({{ team.zekken_number }})<br/><p class="score"><span class="span2">合計得点:{{ team.point }}</span> <span class="span2">内減点: {{ team.late_point }}</span></p>
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</ol>
|
||||||
|
<div v-if="top_three_flag == true">
|
||||||
|
<div class="box2" v-for="(teams, index) in team_list">
|
||||||
|
<h3> {{ index }} </h3>
|
||||||
|
<ol>
|
||||||
|
<div class="best3" v-for="team in teams">
|
||||||
|
<span class="span6"><li class="best2"></span>
|
||||||
|
{{ team.team_name }}({{ team.zekken_number }})<br/><p class="score">合計得点:{{ team.point }} 内減点: {{ team.late_point }}</p>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.1/axios.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
|
||||||
|
<script>
|
||||||
|
axios.defaults.baseURL = 'https://rogaining.sumasen.net/gifuroge';
|
||||||
|
axios.default.withCridentials = true;
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var vm = new Vue({
|
||||||
|
el: '#ranking',
|
||||||
|
data: {
|
||||||
|
selectedEvent: "FC岐阜",
|
||||||
|
selectedClass: "top3",
|
||||||
|
team_list: [],
|
||||||
|
top_three_flag: false,
|
||||||
|
three_pop: [],
|
||||||
|
interval: 1
|
||||||
|
},
|
||||||
|
mounted : function(){
|
||||||
|
|
||||||
|
var int = this.interval * 60 * 1000
|
||||||
|
setInterval(function() {this.ranking_view()}.bind(this), int);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
ranking_view: function(){
|
||||||
|
if (this.selectedClass == 'top3'){
|
||||||
|
this.top_three_flag = true
|
||||||
|
var url = "/all_ranking_top3?event=" + this.selectedEvent
|
||||||
|
axios
|
||||||
|
.get(url)
|
||||||
|
.then(response => ( this.team_list = response.data))}
|
||||||
|
else {
|
||||||
|
this.top_three_flag = false
|
||||||
|
var url = "/get_ranking?class=" + this.selectedClass + '&event=' + this.selectedEvent
|
||||||
|
|
||||||
|
axios
|
||||||
|
.get(url)
|
||||||
|
.then(response => ( this.team_list = response.data ))}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</html>
|
||||||
1298
supervisor/html/realtime_monitor.html
Normal file
1298
supervisor/html/realtime_monitor.html
Normal file
File diff suppressed because it is too large
Load Diff
1298
supervisor/html/realtime_monitor_bck.html
Normal file
1298
supervisor/html/realtime_monitor_bck.html
Normal file
File diff suppressed because it is too large
Load Diff
293
supervisor/html/view_photo_list2.html
Normal file
293
supervisor/html/view_photo_list2.html
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ja">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>岐阜ロゲwith FC岐阜 Myアルバム|岐阜aiネットワーク</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="./style.css">
|
||||||
|
<link rel="stylesheet" href="./css/reset.css">
|
||||||
|
<link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet">
|
||||||
|
<script src="https://kit.fontawesome.com/94e0c17dd1.js" crossorigin="anonymous"></script>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap" rel="stylesheet">
|
||||||
|
<!-- drawer.css -->
|
||||||
|
<link rel="stylesheet" href="./css/drawer.min.css">
|
||||||
|
|
||||||
|
<!-- Meta -->
|
||||||
|
<meta name="description" content="岐阜ロゲin岐阜市のMyアルバムページです。">
|
||||||
|
<meta name="keywords" content="FC岐阜ロゲイニング map 岐阜aiネットワーク ran">
|
||||||
|
<meta name="robot" content="index,follow,noarchive">
|
||||||
|
<meta name="author" content="岐阜aiネットワーク">
|
||||||
|
<meta name="language" content="ja">
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="shortcut icon" href="favicon.png">
|
||||||
|
<style>
|
||||||
|
/* ここにCSSスタイルを記述 */
|
||||||
|
.view {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 50px auto 20px;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.view input,
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
select,input{
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#photoList {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
max-width: 1200px;
|
||||||
|
/* margin: 0 10px; */
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-photo {
|
||||||
|
width: 100%;
|
||||||
|
max-width: calc(32.33% - 2px);
|
||||||
|
margin: 5px;
|
||||||
|
/* その他のスタイル */
|
||||||
|
}
|
||||||
|
.viewtop{
|
||||||
|
min-height: calc(50vh - 50px);
|
||||||
|
}
|
||||||
|
@media screen and (max-width:768px) {
|
||||||
|
.event-photo {
|
||||||
|
width: 100%;
|
||||||
|
max-width: calc(31% - 2px);
|
||||||
|
margin: 5px;
|
||||||
|
/* その他のスタイル */
|
||||||
|
}
|
||||||
|
.viewtop{
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<div id="headerContent">
|
||||||
|
<div id="sp_headerContent">
|
||||||
|
<h1><a href="https://www.gifuai.net/"><img src="./img/rogo.png"></a></h1>
|
||||||
|
<a id="headerMenuBtn" href="#"><img src="./img/menu_open.svg" alt="メニューを開く"></a>
|
||||||
|
</div><!--sp_headerContent-->
|
||||||
|
<div id="headerMenu">
|
||||||
|
<h2>メニュー<a id="headerMenuClose" href="#"><img class="close" src="./img/btn_close_02.svg" alt="閉じる"></a>
|
||||||
|
</h2>
|
||||||
|
<!--ナビバー左側-->
|
||||||
|
<div class="left">
|
||||||
|
<ul class="utility">
|
||||||
|
<h1><a href="https://www.gifuai.net/"><img src="./img/rogo.png"></a></h1>
|
||||||
|
</ul>
|
||||||
|
</div><!--left-->
|
||||||
|
|
||||||
|
<!--ナビバー右側-->
|
||||||
|
<div class="right">
|
||||||
|
<ul class="utility">
|
||||||
|
<li><a href="https://www.gifuai.net/">ホーム</a></li>
|
||||||
|
<li><a href="https://www.gifuai.net/?page_id=60043">岐阜ロゲ</a></li>
|
||||||
|
<li><a href="https://www.gifuai.net/?page_id=4427">自治会SNS</a></li>
|
||||||
|
<li><a href="https://www.gifuai.net/?page_id=9370">会員・寄付金募集</a></li>
|
||||||
|
<li><a href="https://www.gifuai.net/?page_id=12434">フォトギャラリー</a></li>
|
||||||
|
<li><a href="https://www.gifuai.net/?page_id=52511">プレスリリース</a></li>
|
||||||
|
</ul>
|
||||||
|
</div><!--right-->
|
||||||
|
</div><!--headerMenu-->
|
||||||
|
</div><!--headerContent-->
|
||||||
|
</header>
|
||||||
|
<div class="to_classification">
|
||||||
|
<div class="to_class_box">
|
||||||
|
<div class="to_class_tebox">
|
||||||
|
<div class="to_class_text">
|
||||||
|
<h1>Myアルバム</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="to_class_img">
|
||||||
|
<img src="./img/title_event.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<section class="viewtop">
|
||||||
|
<section class="view">
|
||||||
|
<!-- イベント選択 -->
|
||||||
|
<select id="eventSelect">
|
||||||
|
<option disabled="disabled" value="">イベント一覧</option>
|
||||||
|
<option selected="selected" value="FC岐阜">FC岐阜</option>
|
||||||
|
<!-- 他のイベントオプションを追加 -->
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- ゼッケン番号入力 -->
|
||||||
|
<input type="text" id="zekkenInput" placeholder="ゼッケン番号">
|
||||||
|
|
||||||
|
<!-- パスワード入力 -->
|
||||||
|
<input type="password" id="passwordInput" placeholder="パスワード">
|
||||||
|
|
||||||
|
<!-- 検索ボタン -->
|
||||||
|
<button onclick="searchPhotos()">写真リストを検索</button>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 結果表示エリア -->
|
||||||
|
<div id="photoList"></div>
|
||||||
|
</section>
|
||||||
|
<footer class="gifu_fotter">
|
||||||
|
<div class="footer_menubox">
|
||||||
|
<div><a href="https://www.gifuai.net/"><img src="./img/rogo.png"></a></div>
|
||||||
|
<div class="footer_menu">
|
||||||
|
<ul class="footer_menulink">
|
||||||
|
<li><a href="https://www.gifuai.net/">ホーム</a></li>
|
||||||
|
<li><a href="https://www.gifuai.net/?page_id=4806">information</a></li>
|
||||||
|
<li><a
|
||||||
|
href="https://docs.google.com/forms/d/e/1FAIpQLScEXBGEZroAR6F8z2OKhjXn74PhZ5bcSheZVlGlGjz12Iu1JA/viewform">お問い合わせ</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="footer_menulogo">
|
||||||
|
<li><a href="https://twitter.com/GifuK7"><img src="./img/Xlogo.svg" alt="Xロゴ"></a></li>
|
||||||
|
<li><a href="https://www.facebook.com/gifu.ai.network/"><img src="./img/facebook_logo.svg"
|
||||||
|
alt="Facebookロゴ"></a></li>
|
||||||
|
<li><a href="https://www.instagram.com/gifuainetwork/?igshid=MzMyNGUyNmU2YQ%3D%3D"><img
|
||||||
|
src="./img/Instagram_logo.svg" alt="instagramロゴ"></a></li>
|
||||||
|
<li><a href=""><img></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="f_copy">Copyright©NPO岐阜aiネットワーク</div>
|
||||||
|
</footer>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
function searchPhotos() {
|
||||||
|
var selectedEvent = document.getElementById('eventSelect').value;
|
||||||
|
var selectedZekken = document.getElementById('zekkenInput').value;
|
||||||
|
var inputedPassword = document.getElementById('passwordInput').value;
|
||||||
|
|
||||||
|
// login関数を実行して写真リストを取得
|
||||||
|
login(selectedEvent, selectedZekken, inputedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function Login(selectedEvent, selectedZekken, inputedPassword) {
|
||||||
|
const event = selectedEvent;
|
||||||
|
const identifier = selectedZekken;
|
||||||
|
const password = inputedPassword;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/login/', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
identifier: identifier, // メールアドレスまたはゼッケン番号
|
||||||
|
password: password
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(data.error || 'ログインに失敗しました');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ログイン成功時の処理
|
||||||
|
localStorage.setItem('authToken', data.token);
|
||||||
|
localStorage.setItem('userData', JSON.stringify(data.user));
|
||||||
|
|
||||||
|
var URL = "https://rogaining.sumasen.net/api/get-photolist?event=" + selectedEvent + "&zekken=" + selectedZekken + "&pw=" + inputedPassword;
|
||||||
|
|
||||||
|
axios.get(URL)
|
||||||
|
.then(function (response) {
|
||||||
|
displayPhotos(response.data); // 写真リストを表示する関数にレスポンスオブジェクトを渡す
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.error("login function error: ", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// エラーメッセージを表示
|
||||||
|
errorMessage.textContent = error.message;
|
||||||
|
errorMessage.style.display = 'block';
|
||||||
|
} finally {
|
||||||
|
// 送信ボタンを再度有効化
|
||||||
|
submitButton.disabled = false;
|
||||||
|
submitButton.textContent = 'ログイン';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// login関数内で写真リストをDOMに表示する処理を追加
|
||||||
|
function login_old(selectedEvent, selectedZekken, inputedPassword) {
|
||||||
|
var URL = "https://rogaining.sumasen.net/api/get-photolist?event=" + selectedEvent + "&zekken=" + selectedZekken + "&pw=" + inputedPassword;
|
||||||
|
|
||||||
|
axios.get(URL)
|
||||||
|
.then(function (response) {
|
||||||
|
displayPhotos(response.data); // 写真リストを表示する関数にレスポンスオブジェクトを渡す
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
console.error("login function error: ", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 写真リストを表示する関数
|
||||||
|
function displayPhotos(response) {
|
||||||
|
var photoListDiv = document.getElementById('photoList');
|
||||||
|
photoListDiv.innerHTML = ''; // 既存の内容をクリア
|
||||||
|
|
||||||
|
// レスポンス全体をログに出力
|
||||||
|
console.log('Response object:', response);
|
||||||
|
|
||||||
|
// レスポンスオブジェクトからphoto_list配列を取得
|
||||||
|
var photos = response.photo_list;
|
||||||
|
|
||||||
|
// photo_listの内容をログに出力
|
||||||
|
console.log('Photo list array:', photos);
|
||||||
|
|
||||||
|
// 'photos'が配列であることを確認
|
||||||
|
if (Array.isArray(photos)) {
|
||||||
|
photos.forEach(function (photodata,index) {
|
||||||
|
// 各写真のURLをインデックス付きでログに出力
|
||||||
|
console.log(`Photo ${index + 1} data:`, photodata);
|
||||||
|
|
||||||
|
// photodataのプロパティの存在確認とcp_numberの条件チェック
|
||||||
|
if (!photodata.hasOwnProperty('photo_url') ||
|
||||||
|
!photodata.hasOwnProperty('cp_number') ||
|
||||||
|
photodata.cp_number <= 0) { // cp_numberが0以下の場合はスキップ
|
||||||
|
console.log(`Skipping photo at index ${index}. cp_number: ${photodata.cp_number}`);
|
||||||
|
return; // この写真をスキップ
|
||||||
|
}
|
||||||
|
|
||||||
|
// img要素を作成
|
||||||
|
var img = document.createElement('img');
|
||||||
|
img.src = photodata.photo_url; // 写真のURLをsrc属性に設定
|
||||||
|
img.className = 'event-photo'; // クラス名を設定
|
||||||
|
img.alt = 'Photo cp=${photodata.cp_number}'; // 代替テキストを設定
|
||||||
|
|
||||||
|
// 画像の読み込みエラーをキャッチ
|
||||||
|
img.onerror = function() {
|
||||||
|
console.error(`Failed to load image ${index + 1}:`, photodata.photorl);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 画像を表示エリアに追加
|
||||||
|
photoListDiv.appendChild(img);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
photoListDiv.innerHTML = 'ゼッケン番号とパスワードが一致していません。もう一度入力をお願いします。'
|
||||||
|
console.error('Expected photos to be an array, but received:', photos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<script src="jquery-2.1.3.min.js"></script>
|
||||||
|
|
||||||
|
<script src="./js/main.js"></script>
|
||||||
|
<link rel="stylesheet" href="./js/drawer.min.js">
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user