feat. member.page.profile 完善

This commit is contained in:
ukyo 2025-01-23 14:40:02 +08:00
parent d022b0635a
commit 149d603a4c
12 changed files with 428 additions and 18 deletions

View File

@ -9,7 +9,7 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Log; use Log;
use Maatwebsite\Excel\Facades\Excel; use Maatwebsite\Excel\Facades\Excel;
use App\Models\User;
class SettingController extends Controller class SettingController extends Controller
{ {
@ -62,4 +62,26 @@ public function promoCodeCreate(Request $request)
} }
} }
public function adminIndex(Request $request)
{
$data = User::paginate(15);
return view('admin.setting.adminlist', ['data' => $data]);
}
public function loginStatus(Request $request)
{
$user = User::where('id', $request->id)->first();
$user->can_login = $request->can_login;
$user->save();
return response()->json([
'status' => 'success',
'msg' => '狀態已更新',
'can_login' => $user->can_login,
]);
}
} }

View File

@ -42,6 +42,7 @@ public function redirectToProvider(Request $request)
*/ */
public function handleProviderCallback(Request $request) public function handleProviderCallback(Request $request)
{ {
// 从 Session 获取 $redirectTo // 从 Session 获取 $redirectTo
$redirectTo = session('redirect_to', 'member'); $redirectTo = session('redirect_to', 'member');
@ -70,6 +71,7 @@ public function handleProviderCallback(Request $request)
'avatar' => $avatar, 'avatar' => $avatar,
'source' => 'cafeg', 'source' => 'cafeg',
'email' => $email, 'email' => $email,
'can_login' => 0,
]); ]);
Auth::guard('web')->login($newUser); Auth::guard('web')->login($newUser);
} }

View File

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Auth;
class AdminAuth
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (Auth::check() && Auth::user()->can_login == 1) {
return $next($request);
}
return redirect()->route('admin.login')->with('error', '此帳號未開通,請聯繫管理員');
}
}

View File

@ -20,7 +20,7 @@ public function handle(Request $request, Closure $next): Response
if (!Auth::check() && $request->path() != 'admin/register') { if (!Auth::check() && $request->path() != 'admin/register') {
return redirect()->route('admin.login'); return redirect()->route('admin.login');
} }
if (Auth::check() && $request->path() == 'admin/login') { if (Auth::check() && $request->path() == 'admin/login' && Auth::user()->can_login == 1) {
return redirect()->route('admin.index'); return redirect()->route('admin.index');
} }

View File

@ -66,5 +66,11 @@ public function getPromoCode()
{ {
return $this->hasOne(Promocode::class, 'used_count', 'id'); return $this->hasOne(Promocode::class, 'used_count', 'id');
} }
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
} }

View File

@ -23,6 +23,7 @@ class User extends Authenticatable
'phone', 'phone',
'password', 'password',
'line_id', 'line_id',
'can_login',
]; ];
/** /**
@ -34,6 +35,13 @@ class User extends Authenticatable
'remember_token', 'remember_token',
]; ];
public static $canlogin =
[
0 => '未允許',
1 => '允許',
];
/** /**
* Get the attributes that should be cast. * Get the attributes that should be cast.
* *
@ -46,4 +54,11 @@ protected function casts(): array
'password' => 'hashed', 'password' => 'hashed',
]; ];
} }
public function getCanLoginStatusAttribute()
{
return self::$canlogin[$this->attributes['can_login']] ?? '未知狀態';
}
} }

View File

@ -21,6 +21,11 @@
"url": "admin/setting/promocode", "url": "admin/setting/promocode",
"name": "優惠代碼", "name": "優惠代碼",
"slug": "setting-promocode" "slug": "setting-promocode"
},
{
"url": "admin/setting/adminlist",
"name": "後台登入人員",
"slug": "setting-adminlist"
} }
] ]
}, },

View File

@ -231,7 +231,24 @@
</body> </body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
@if (session('error'))
<script>
document.addEventListener("DOMContentLoaded", function() {
console.log('go there');
Swal.fire({
title: '失敗'
, text: "{{ session('error') }}"
, icon: 'error'
, confirmButtonText: '确定'
});
});
</script>
@endif
</body> </body>
</html> </html>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>

View File

@ -0,0 +1,235 @@
@extends('layouts.admin_app')
@section('header')
@endsection
@section('content')
<style>
.table th,
.table td {
white-space: nowrap;
/* 禁止文字換行 */
text-align: center;
/* 內容置中 */
overflow: hidden;
/* 隱藏超出內容 */
text-overflow: ellipsis;
/* 超出用省略號顯示 */
}
.table th {
width: auto;
/* 可根據內容自適應,但不要超出 */
}
.table {
width: 100%;
/* 撐滿父容器 */
}
</style>
<div clas="content-wrapper">
<div class="row">
<div class="col-md-12">
<div class="card-body">
<!-- Basic Breadcrumb -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a href="javascript:void(0);">首頁</a>
</li>
<li class="breadcrumb-item">
<a href="javascript:void(0);">設定</a>
</li>
<li class="breadcrumb-item active">優惠代碼</li>
</ol>
</nav>
</div>
</div>
<!-- Content wrapper -->
<div class="content-wrapper">
<!-- Content MemberList-->
<div class="container-xxl flex-grow-1 container-p-y">
<div class="row g-6 mb-6">
<div class="col-sm-6 col-xl-3">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between">
<div class="me-1">
<p class="text-heading mb-1">會員共計</p>
<div class="d-flex align-items-center">
<h4 class="mb-1 me-2">21,459</h4>
<p class="text-success mb-1">(+29%)</p>
</div>
<small class="mb-0">Total Users</small>
</div>
<div class="avatar">
<div class="avatar-initial bg-label-primary rounded-3">
<div class="ri-group-line ri-26px"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between">
<div class="me-1">
<p class="text-heading mb-1">普通會員</p>
<div class="d-flex align-items-center">
<h4 class="mb-1 me-1">4,567</h4>
<p class="text-success mb-1">(+18%)</p>
</div>
<small class="mb-0">Last week analytics</small>
</div>
<div class="avatar">
<div class="avatar-initial bg-label-danger rounded-3">
<div class="ri-user-add-line ri-26px"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between">
<div class="me-1">
<p class="text-heading mb-1">白銀會員</p>
<div class="d-flex align-items-center">
<h4 class="mb-1 me-1">19,860</h4>
<p class="text-danger mb-1">(-14%)</p>
</div>
<small class="mb-0">Last week analytics</small>
</div>
<div class="avatar">
<div class="avatar-initial bg-label-success rounded-3">
<div class="ri-user-follow-line ri-26px"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="card">
<div class="card-body">
<div class="d-flex justify-content-between">
<div class="me-1">
<p class="text-heading mb-1">白金會員</p>
<div class="d-flex align-items-center">
<h4 class="mb-1 me-1">237</h4>
<p class="text-success mb-1">(+42%)</p>
</div>
<small class="mb-0">Last week analytics</small>
</div>
<div class="avatar">
<div class="avatar-initial bg-label-warning rounded-3">
<div class="ri-user-search-line ri-26px"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Users List Table -->
<div class="card">
<div class="card-header border-bottom">
<h5 class="card-title mb-0">Filters</h5>
<div class="d-flex justify-content-between align-items-center row gx-5 pt-4 gap-5 gap-md-0">
<div class="col-md-4 user_role"></div>
<div class="col-md-4 user_plan"></div>
<div class="col-md-4 user_status"></div>
</div>
</div>
<div class="card-datatable table-responsive">
<table class="datatables-users table">
<thead>
<tr>
<th>會員id</th>
<th>會員名稱</th>
<th>手機號碼</th>
<th>登入狀態</th>
<th> </th>
</tr>
</thead>
<tbody>
@foreach ($data as $item)
<tr>
<td>{{ $item->id }}</td>
<td>{{ $item->name }}</td>
<td>{{ $item->phone }}</td>
<td>
<a href="#">
<span id="status-{{ $item->id }}"
onclick="updateStatus({{ $item->id }}, {{ $item->can_login == 1 ? 0 : 1 }})"
class="badge rounded-pill {{ $item->can_login == 1 ? 'bg-label-primary' : 'bg-label-danger' }}">
{{ $item->can_login_status}}
</span>
</a>
</td>
<td></td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="content-backdrop fade"></div>
{{ $data->links() }}
</div>
<!-- Content wrapper -->
</div>
<!-- / Layout page -->
</div>
</div>
@endsection
@section('scripts')
<script>
function updateStatus(id, status) {
$.ajax({
type: "patch",
url: "{{ route('admin.canlogin.status') }}",
data: {
id: id,
can_login: status,
_token: "{{ csrf_token() }}" // 確保 CSRF token 被傳送
},
dataType: "json",
success: function(response) {
console.log('can_login status:', response.status);
Swal.fire({
title: '成功',
text: response.msg,
icon: 'success',
confirmButtonText: '确定'
});
const statusElement = document.getElementById(`status-${id}`);
if (statusElement) {
statusElement.textContent = response.can_login == 1 ? '允許' : '未允許';
statusElement.className = `badge rounded-pill ${response.can_login == 1 ? 'bg-label-primary' : 'bg-label-danger'}`;
statusElement.setAttribute('onclick', `updateStatus(${id}, ${response.can_login == 1 ? 0 : 1})`);
}
},
error: function(xhr) {
Swal.fire({
title: '錯誤',
text: '更新失敗,請稍後再試。',
icon: 'error',
confirmButtonText: '确定'
});
}
});
}
</script>
@endsection

View File

@ -415,6 +415,88 @@ function sendVerificationEmail(email) {
window.location.href = "{{ route('member.index') }}"; window.location.href = "{{ route('member.index') }}";
}); });
$(document).ready(function() {
// 監聽表單提交事件
$('#editUserForm').on('submit', function(e) {
e.preventDefault(); // 阻止默認的表單提交行為
// 獲取表單數據
let email = $('#formValidationEmail').val();
let password = $('#basic-default-password').val();
let confirmPassword = $('#formValidationConfirmPass').val();
let phone = $('#modalEditUserPhone').val();
// 表單驗證
let valid = true;
// 驗證 Email
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
valid = false;
$('#formValidationEmail').addClass('is-invalid');
} else {
$('#formValidationEmail').removeClass('is-invalid');
}
// 驗證密碼與確認密碼是否相符
if (password !== confirmPassword) {
valid = false;
$('#formValidationConfirmPass').addClass('is-invalid');
} else {
$('#formValidationConfirmPass').removeClass('is-invalid');
}
// 驗證手機號碼格式
const phoneRegex = /^09\d{8}$/;
if (!phoneRegex.test(phone)) {
valid = false;
$('#modalEditUserPhone').addClass('is-invalid');
} else {
$('#modalEditUserPhone').removeClass('is-invalid');
}
// 若驗證通過,發送 AJAX 請求
if (valid) {
// 打包數據
let formData = {
email: email,
password: password,
phone: phone
};
$.ajax({
url: "{{ route('member.profile.update') }}", // 替換為後端處理路由
type: 'PUT', // 使用 HTTP PUT 方法
data: formData,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') // CSRF 驗證
},
success: function(response) {
// 成功處理邏輯
Swal.fire({
title: '更新成功',
text: response.message || '您的個人資訊已成功更新!',
icon: 'success',
confirmButtonText: '確定'
}).then(() => {
// 跳轉至其他頁面
window.location.href = "{{ route('member.index') }}";
});
},
error: function(xhr, status, error) {
// 錯誤處理邏輯
Swal.fire({
title: '更新失敗',
text: xhr.responseJSON.message || '請稍後重試!',
icon: 'error',
confirmButtonText: '確定'
});
}
});
}
});
});
</script> </script>
@endsection @endsection
@endsection @endsection

View File

@ -1,6 +1,6 @@
<aside id="layout-menu" class="layout-menu menu-vertical menu bg-menu-theme"> <aside id="layout-menu" class="layout-menu menu-vertical menu bg-menu-theme">
<div class="app-brand demo"> <div class="app-brand demo">
<a href="{{url('/')}}" class="app-brand-link"> <a href="{{ route('admin.index') }}" class="app-brand-link">
<span class="app-brand-logo demo"> <span class="app-brand-logo demo">
<span style="color: var(--bs-primary)"> <span style="color: var(--bs-primary)">
<img src="{{asset('assets/img/logo/cafeg-logo.png')}}" width="50px" height="50px" alt="{{asset('img/logo/cafeg-logo.png')}}"> </img> <img src="{{asset('assets/img/logo/cafeg-logo.png')}}" width="50px" height="50px" alt="{{asset('img/logo/cafeg-logo.png')}}"> </img>

View File

@ -1,6 +1,8 @@
<?php <?php
use App\Http\Controllers\LoginController; use App\Http\Controllers\LoginController;
use App\Http\Middleware\AdminRedirect; use App\Http\Middleware\AdminRedirect;
use App\Http\Middleware\AdminAuth;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
/*********************************************** /***********************************************
@ -14,7 +16,7 @@
Route:: Route::
namespace('App\Http\Controllers\Admin') // 設置命名空間 namespace('App\Http\Controllers\Admin') // 設置命名空間
->middleware(AdminRedirect::class) ->middleware([AdminRedirect::class, AdminAuth::class])
->group(function () { ->group(function () {
Route::get('/', 'IndexController@index')->name('index'); Route::get('/', 'IndexController@index')->name('index');
Route::get('register', 'RegisterController@index')->name('register'); Route::get('register', 'RegisterController@index')->name('register');
@ -30,9 +32,8 @@
Route::get('promocode', 'SettingController@promoCode')->name('promocode'); Route::get('promocode', 'SettingController@promoCode')->name('promocode');
Route::get('promocode/used', 'SettingController@promoCode')->name('promocode.status'); Route::get('promocode/used', 'SettingController@promoCode')->name('promocode.status');
Route::post('promocode', 'SettingController@promoCodeCreate')->name('promocode.create'); Route::post('promocode', 'SettingController@promoCodeCreate')->name('promocode.create');
Route::get('adminlist', 'SettingController@adminIndex')->name('adminlist.index');
Route::patch('canlogin/status', 'SettingController@loginStatus')->name('canlogin.status');
}); });
}); });
// line 登入後查詢; // line 登入後查詢;