增加 用户组功能

This commit is contained in:
iVampireSP.com 2022-11-26 21:52:30 +08:00
parent 653844751b
commit a49ce2c5c0
No known key found for this signature in database
GPG Key ID: 2F7B001CA27A8132
15 changed files with 599 additions and 76 deletions

View File

@ -7,6 +7,7 @@
use App\Models\Host;
use App\Models\Transaction;
use App\Models\User;
use App\Models\UserGroup;
use App\Models\WorkOrder\WorkOrder;
use Carbon\Carbon;
use Illuminate\Http\RedirectResponse;
@ -37,7 +38,7 @@ public function index(Request $request): View
$users = $users->where('email', 'like', '%' . $request->email . '%');
}
$users = $users->paginate(100);
$users = $users->with('user_group')->paginate(100);
return view('admin.users.index', compact('users'));
}
@ -70,9 +71,9 @@ public function edit(User $user)
$hosts = Host::where('user_id', $user->id)->latest()->paginate(50, ['*'], 'hosts_page');
$workOrders = WorkOrder::where('user_id', $user->id)->latest()->paginate(50, ['*'], 'workOrders_page');
$balances = Balance::where('user_id', $user->id)->latest()->paginate(50, ['*'], 'balances_page');
$groups = UserGroup::all();
return view('admin.users.edit', compact('user', 'hosts', 'workOrders', 'balances'));
return view('admin.users.edit', compact('user', 'hosts', 'workOrders', 'balances', 'groups'));
}
/**
@ -118,7 +119,10 @@ public function update(Request $request, User $user)
}
}
$user->save();
if ($request->has('user_group_id')) {
$user->user_group_id = $request->user_group_id;
}
if ($user->isDirty()) {
$user->save();

View File

@ -0,0 +1,123 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Models\UserGroup;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class UserGroupController extends Controller
{
/**
* Display a listing of the resource.
*
* @return View
*/
public function index()
{
$user_groups = UserGroup::paginate(10);
return view('admin.user-groups.index', compact('user_groups'));
}
/**
* Show the form for creating a new resource.
*
* @return View
*/
public function create(): View
{
//
return view('admin.user-groups.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
{
$request->validate($this->rules());
$user_group = UserGroup::create($request->all());
return redirect()->route('admin.user-groups.edit', $user_group)->with('success', '用户组新建成功。');
}
/**
* Display the specified resource.
*
* @param UserGroup $user_group
*
* @return View
*/
public function show(UserGroup $user_group): View
{
$users = User::where('user_group_id', $user_group->id)->paginate(100);
return view('admin.user-groups.show', compact('user_group', 'users'));
}
/**
* Show the form for editing the specified resource.
*
* @param UserGroup $user_group
*
* @return View
*/
public function edit(UserGroup $user_group): View
{
//
return view('admin.user-groups.edit', compact('user_group'));
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param UserGroup $user_group
*
* @return RedirectResponse
*/
public function update(Request $request, UserGroup $user_group): RedirectResponse
{
//
$request->validate($this->rules());
$user_group->update($request->all());
return back()->with('success', '用户组更新成功。');
}
/**
* Remove the specified resource from storage.
*
* @param UserGroup $user_group
*
* @return RedirectResponse
*/
public function destroy(UserGroup $user_group)
{
$user_group->delete();
return redirect()->route('admin.user-groups.index')->with('success', '用户组删除成功。');
}
private function rules(): array
{
return [
'name' => 'required|string',
'color' => 'regex:/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/',
'discount' => 'required|numeric|min:0|max:100',
'exempt' => 'required|boolean',
];
}
}

View File

@ -34,7 +34,14 @@ public function __construct($minute)
public function handle()
{
// chunk hosts and load user
Host::where('minute_at', $this->minute)->whereIn('status', ['running', 'stopped'])->with('user')->chunk(500, function ($hosts) {
$host = new Host();
// if env not local, then use minute_at
if (app()->environment() != 'local') {
$host = $host->where('minute_at', $this->minute);
}
$host->whereIn('status', ['running', 'stopped'])->with('user')->chunk(500, function ($hosts) {
foreach ($hosts as $host) {
$host->cost();
}

View File

@ -218,6 +218,15 @@ public function safeDelete(): bool
public function cost($amount = null, $auto = true): bool
{
$this->load('user');
$user = $this->user;
$user->load('user_group');
$user_group = $user->user_group;
if ($user_group) {
if ($user_group->exempt) {
return true;
}
}
$real_price = $amount ?? $this->price;
@ -227,6 +236,13 @@ public function cost($amount = null, $auto = true): bool
}
}
if ($user_group) {
if ($user_group->discount !== 100 && $user_group->discount !== null) {
$real_price = $real_price * ($user_group->discount / 100);
}
}
if ($auto) {
// 获取本月天数
$days = now()->daysInMonth;

View File

@ -7,6 +7,7 @@
use App\Exceptions\User\BalanceNotEnoughException;
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
@ -144,6 +145,11 @@ public function hosts(): HasMany
return $this->hasMany(Host::class);
}
public function user_group(): BelongsTo
{
return $this->belongsTo(UserGroup::class);
}
/**
* @throws CommonException
* @throws BalanceNotEnoughException

30
app/Models/UserGroup.php Normal file
View File

@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class UserGroup extends Model
{
use HasFactory;
public $fillable = [
'name',
'color',
'discount',
'exempt',
];
public $casts = [
'discount' => 'integer',
'exempt' => 'boolean',
];
public function users(): HasMany
{
return $this->hasMany(User::class);
}
}

View File

@ -0,0 +1,73 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('user_groups', function (Blueprint $table) {
$table->id();
// 名称
$table->string('name')->comment('名称')->index();
// 颜色
$table->string('color')->comment('颜色')->nullable();
// 优惠百分比
$table->integer('discount')->comment('优惠百分比')->default(100);
// 暂停/终止豁免权
$table->boolean('exempt')->comment('暂停/终止豁免权')->default(false);
$table->timestamps();
});
// Schema::table('users', function (Blueprint $table) {
// $table->unsignedBigInteger('user_group_id')->nullable()->comment('用户组')->index()->after('banned_reason');
// $table->foreign('user_group_id')->references('id')->on('user_groups')->onDelete('set null');
// });
Schema::table('users', function (Blueprint $table) {
$table->unsignedBigInteger('user_group_id')->nullable()->comment('用户组')->index()->after('banned_reason');
$table->foreign('user_group_id')->references('id')->on('user_groups')->onDelete('set null');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
// Schema::table('users', function (Blueprint $table) {
// // drop column if exists
// if (Schema::hasColumn('users', 'user_group_id')) {
// $table->dropForeign('users_user_group_id_foreign');
//
// $table->dropColumn('user_group_id');
// }
// });
//
Schema::table('users', function (Blueprint $table) {
// drop column if exists
if (Schema::hasColumn('users', 'user_group_id')) {
$table->dropForeign('users_user_group_id_foreign');
$table->dropColumn('user_group_id');
}
});
Schema::dropIfExists('user_groups');
}
};

View File

@ -0,0 +1,43 @@
@extends('layouts.admin')
@section('title', '新建用户组')
@section('content')
<h3>新建用户组</h3>
<a class="mt-3" href="{{ route('admin.user-groups.index') }}">返回 用户组列表</a>
<form method="POST" action="{{ route('admin.user-groups.store')}}">
@csrf
<div class="form-group mt-1">
<label for="name">名称</label>
<input type="text" class="form-control" id="name" name="name" placeholder="名称" required>
</div>
<div class="form-group mt-1 col-1">
<label for="color">颜色</label>
<input type="color" class="form-control" id="color" name="color" required>
</div>
<div class="form-group mt-1">
<label for="discount">折扣 (%)</label>
<input type="number" class="form-control" id="discount" name="discount" placeholder="折扣" value="100"
required>
{{-- 提示 --}}
<small class="form-text text-muted">折扣为 100% 时,不打折。</small>
</div>
<div class="form-group mt-1">
<label for="exempt">暂停 / 终止豁免权</label>
<select class="form-control" id="exempt" name="exempt" required>
<option value="0"></option>
<option value="1"></option>
</select>
</div>
<button type="submit" class="btn btn-primary mt-3">提交</button>
</form>
@endsection

View File

@ -0,0 +1,53 @@
@extends('layouts.admin')
@section('title', '用户组: ' . $user_group->name)
@section('content')
<h3>{{ $user_group->name }}</h3>
<a href="{{ route('admin.user-groups.show', $user_group) }}">查看此用户组</a>
<form method="POST" action="{{ route('admin.user-groups.update', $user_group)}}">
@csrf
@method('PATCH')
<div class="form-group mt-1">
<label for="name">名称</label>
<input type="text" class="form-control" id="name" name="name" value="{{ $user_group->name }}"
placeholder="{{ $user_group->name }}" required>
</div>
<div class="form-group mt-1 col-1">
<label for="color">颜色</label>
<input type="color" class="form-control" id="color" name="color" value="{{ $user_group->color }}" required>
</div>
<div class="form-group mt-1">
<label for="discount">折扣 (%)</label>
<input type="number" class="form-control" id="discount" name="discount" placeholder="折扣"
value="{{ $user_group->discount }}" required>
{{-- 提示 --}}
<small class="form-text text-muted">折扣为 100% 时,不打折。</small>
</div>
<div class="form-group mt-1">
<label for="exempt">暂停 / 终止豁免权</label>
<select class="form-control" id="exempt" name="exempt" required>
<option value="0"></option>
<option value="1" @if ($user_group->exempt) selected @endif></option>
</select>
</div>
<button type="submit" class="btn btn-primary mt-3">提交</button>
</form>
<hr/>
<form method="POST" action="{{ route('admin.user-groups.destroy', $user_group)}}"
onsubmit="return confirm('此用户组将不复存在。所有加入此用户组的用户将会回归默认。')">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">删除</button>
</form>
@endsection

View File

@ -0,0 +1,60 @@
@extends('layouts.admin')
@section('title', '用户组')
@section('content')
<h3>用户组</h3>
<p>将用户划分到一个组中,可让他们享受到特别待遇。</p>
<a href="{{ route('admin.user-groups.create') }}">新用户组</a>
<div class="overflow-auto mt-3">
<table class="table table-hover">
<thead>
<th>ID</th>
<th>名称</th>
<th>折扣</th>
<th>暂停 / 终止豁免权</th>
<th>操作</th>
</thead>
<tbody>
@foreach ($user_groups as $user_group)
<tr>
<td>
<a href="{{ route('admin.user-groups.edit', $user_group) }}">
{{ $user_group->id }}
</a>
</td>
<td>
<span class="badge badge-pill" style="background-color: {{ $user_group->color }}">&nbsp;</span>
&nbsp;
{{ $user_group->name }}
</td>
<td>
@if ($user_group->discount == 100)
不享受折扣
@else
享受 {{ $user_group->discount }}% 的折扣
@endif
</td>
<td>
{{ $user_group->exempt ? '是' : '否' }}
</td>
<td>
<a href="{{ route('admin.user-groups.show', $user_group) }}"
class="btn btn-primary btn-sm">查看</a>
<a href="{{ route('admin.user-groups.edit', $user_group) }}"
class="btn btn-primary btn-sm">编辑</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
{{-- 分页 --}}
{{ $user_groups->links() }}
@endsection

View File

@ -0,0 +1,70 @@
@extends('layouts.admin')
@section('title', '用户组: ' . $user_group->name)
@section('content')
<h3>{{ $user_group->name }}</h3>
<a href="{{ route('admin.user-groups.edit', $user_group) }}">编辑此用户组</a>
<br/>
<span>
此用户组的 ID {{ $user_group->id }}
@if ($user_group->discount == 100)
不享受折扣
@else
享受 {{ $user_group->discount }}% 的折扣
@endif
@if ($user_group->exempt)
并且有暂停 / 终止豁免权
@else
并且没有暂停 / 终止豁免权
@endif
</span>
{{-- 用户列表 --}}
<div class="overflow-auto">
<table class="table table-hover">
<thead>
<th>ID</th>
<th>用户名</th>
<th>邮箱</th>
<th>余额</th>
<th>注册时间</th>
<th>操作</th>
</thead>
<tbody>
@foreach ($users as $user)
<tr>
<td>
<a href="{{ route('admin.users.show', $user) }}" title="切换到 {{ $user->name }}">
{{ $user->id }}
</a>
</td>
<td>
<a href="{{ route('admin.users.edit', $user) }}"
title="显示和编辑 {{ $user->name }} 的资料">
{{ $user->name }}
</a>
</td>
<td>
{{ $user->email }}
</td>
<td>
{{ $user->balance }}
</td>
<td>
{{ $user->created_at }}
</td>
<td>
<a href="{{ route('admin.users.edit', $user) }}" class="btn btn-primary btn-sm">编辑</a>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endsection

View File

@ -43,7 +43,7 @@
<span>{{ $host->getPrice() }} </span>
</td>
<td>
<x-host-status :status="$host->status" />
<x-host-status :status="$host->status"/>
</td>
<td>
<a href="{{ route('admin.hosts.edit', $host) }}" class="btn btn-primary btn-sm">查看</a>
@ -165,4 +165,25 @@
<button type="submit" class="btn btn-primary mt-3">提交</button>
</form>
{{-- 用户组 --}}
<h3 class="mt-3">用户组</h3>
<form action="{{ route('admin.users.update', $user) }}" method="post">
@csrf
@method('PATCH')
<div class="form-group">
<label for="group_id">用户组</label>
<select class="form-control" id="group_id" name="user_group_id">
<option value=""></option>
@foreach($groups as $group)
<option value="{{ $group->id }}"
@if ($user->user_group_id == $group->id) selected @endif>{{ $group->name }}</option>
@endforeach
</select>
</div>
<button type="submit" class="btn btn-primary mt-3">提交</button>
</form>
@endsection

View File

@ -37,6 +37,7 @@
<th>用户名</th>
<th>邮箱</th>
<th>余额</th>
<th>用户组</th>
<th>注册时间</th>
<th>操作</th>
</thead>
@ -60,6 +61,15 @@
<td>
{{ $user->balance }}
</td>
<td>
@if ($user->group_id)
<a href="{{ route('admin.user-groups.show', $user->user_group_id) }}">
{{ $user->user_group->name }}
</a>
@else
@endif
</td>
<td>
{{ $user->created_at }}
</td>

View File

@ -19,94 +19,97 @@
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ route('admin.index') }}">
管理员
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
<div id="app">
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ route('admin.index') }}">
管理员
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.users.index') }}">用户</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.hosts.index') }}">主机</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.modules.index') }}">模块</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.work-orders.index') }}">工单</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.transactions') }}">交易记录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.admins.index') }}">管理员</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.commands') }}">命令速查表</a>
</li>
</ul>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.users.index') }}">用户</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.hosts.index') }}">主机</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.modules.index') }}">模块</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.work-orders.index') }}">工单</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.transactions') }}">交易记录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.admins.index') }}">管理员</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.user-groups.index') }}">用户组</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.commands') }}">命令速查表</a>
</li>
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ms-auto">
<!-- Authentication Links -->
@if (!Auth::guard('admin')->check())
@if (Route::has('admin.login'))
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.login') }}">{{ __('Login') }}</a>
</li>
@endif
@else
@if (Auth::guard('web')->check())
<li class="nav-item">
<a class="nav-link" href="{{ route('index') }}">切换到
{{ Auth::guard('web')->user()->name }}</a>
</li>
@endif
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ Auth::guard('admin')->user()->email ?? '' }}
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ms-auto">
<!-- Authentication Links -->
@if (!Auth::guard('admin')->check())
@if (Route::has('admin.login'))
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.login') }}">{{ __('Login') }}</a>
</li>
@endif
@else
@if (Auth::guard('web')->check())
<li class="nav-item">
<a class="nav-link" href="{{ route('index') }}">切换到
{{ Auth::guard('web')->user()->name }}</a>
</li>
@endif
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ Auth::guard('admin')->user()->email ?? '' }}
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('admin.logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('admin.logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('admin.logout') }}" method="POST"
class="d-none">
@csrf
</form>
</div>
</li>
@endguest
<form id="logout-form" action="{{ route('admin.logout') }}" method="POST"
class="d-none">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>
<main class="py-4">
<x-alert />
<x-alert/>
<div class="container">
@yield('content')
</div>
</main>
<x-module-script />
<x-module-script/>
</div>
</body>

View File

@ -6,6 +6,7 @@
use App\Http\Controllers\Admin\HostController;
use App\Http\Controllers\Admin\ModuleController;
use App\Http\Controllers\Admin\UserController;
use App\Http\Controllers\Admin\UserGroupController;
use App\Http\Controllers\Admin\WorkOrderController;
use Illuminate\Support\Facades\Route;
@ -26,6 +27,9 @@
Route::resource('hosts', HostController::class)->only(['index', 'edit', 'update', 'destroy']);
Route::resource('work-orders', WorkOrderController::class)->only(['index', 'show', 'edit', 'update', 'destroy']);
Route::resource('user-groups', UserGroupController::class);
Route::view('commands', 'admin.commands')->name('commands');
Route::get('transactions', [HomeController::class, 'transactions'])->name('transactions');