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 Log;
use Maatwebsite\Excel\Facades\Excel;
use App\Models\User;
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)
{
// 从 Session 获取 $redirectTo
$redirectTo = session('redirect_to', 'member');
@ -70,6 +71,7 @@ public function handleProviderCallback(Request $request)
'avatar' => $avatar,
'source' => 'cafeg',
'email' => $email,
'can_login' => 0,
]);
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') {
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');
}

View File

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

View File

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

View File

@ -72,7 +72,7 @@
// Core stylesheets
'core.scss': 'https://demos.pixinvent.com/materialize-html-laravel-admin-template/demo/build/assets/core-kL5gEEKA.css',
'core-dark.scss': 'https://demos.pixinvent.com/materialize-html-laravel-admin-template/demo/build/assets/core-dark-DFvmi5J3.css',
// Themes
'theme-default.scss': 'https://demos.pixinvent.com/materialize-html-laravel-admin-template/demo/build/assets/theme-default-Bt2z4DrM.css',
'theme-default-dark.scss': 'https://demos.pixinvent.com/materialize-html-laravel-admin-template/demo/build/assets/theme-default-dark-CHvAJUy2.css',
@ -93,7 +93,7 @@
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-5J3LMKC" height="0" width="0" style="display: none; visibility: hidden"></iframe></noscript>
<!-- Layout Content -->
<!-- Content -->
<div class="position-relative">
<div class="authentication-wrapper authentication-basic container-p-y p-4 p-sm-0">
@ -105,13 +105,13 @@
<div class="app-brand justify-content-center mt-5">
<a href="#" class="app-brand-link gap-2">
<span class="app-brand-logo demo"><span>
<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>
</span>
</span>
<span class="app-brand-text demo text-heading fw-semibold">
&nbsp; &nbsp; <img src="{{asset('assets/img/logo/cafeg-logo-h.png')}}" width="120px" height="50px" alt="{{asset('img/logo/cafeg-logo.png')}}"> </img>
&nbsp; &nbsp; <img src="{{asset('assets/img/logo/cafeg-logo-h.png')}}" width="120px" height="50px" alt="{{asset('img/logo/cafeg-logo.png')}}"> </img>
</span>
</a>
</div>
@ -166,13 +166,13 @@
</div>
<div class="d-flex justify-content-center gap-2">
<a href="{{route('admin.login.line')}}" class="btn btn-icon rounded-circle btn-text-line">
<i class="tf-icons ri-line-fill"></i>
</a>
{{-- <a href="javascript:;" class="btn btn-icon rounded-circle btn-text-google-plus">
<i class="tf-icons ri-google-fill"></i>
@ -188,11 +188,11 @@
<!--/ Layout Content -->
{{-- <div class="buy-now">
<a href="#" target="" class="btn btn-danger btn-buy-now">Buy Now</a>
</div> --}}
<!-- Include Scripts -->
<!-- $isFront is used to append the front layout scripts only on the front layout otherwise the variable will be blank -->
@ -231,7 +231,24 @@
</body>
<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>
</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') }}";
});
$(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>
@endsection
@endsection

View File

@ -1,6 +1,6 @@
<aside id="layout-menu" class="layout-menu menu-vertical menu bg-menu-theme">
<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 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>

View File

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