first commit
This commit is contained in:
24
modules/User/Events/Login.php
Normal file
24
modules/User/Events/Login.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\User\Events;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class Login
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
public Request $request,
|
||||
public string $token
|
||||
) {
|
||||
}
|
||||
}
|
43
modules/User/Http/Controllers/AuthController.php
Normal file
43
modules/User/Http/Controllers/AuthController.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\User\Http\Controllers;
|
||||
|
||||
use Catch\Base\CatchController as Controller;
|
||||
use Catch\Exceptions\FailedException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Modules\User\Events\Login;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function login(Request $request)
|
||||
{
|
||||
$token = Auth::guard(getGuardName())->attempt($request->only(['email', 'password']));
|
||||
|
||||
Event::dispatch(new Login($request, $token));
|
||||
|
||||
if (! $token) {
|
||||
throw new FailedException('登录失败!请检查邮箱或者密码');
|
||||
}
|
||||
|
||||
return compact('token');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* logout
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
// Auth::guard(Helper::getGuardName())->logout();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
113
modules/User/Http/Controllers/UserController.php
Normal file
113
modules/User/Http/Controllers/UserController.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\User\Http\Controllers;
|
||||
|
||||
use Catch\Base\CatchController as Controller;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Http\Request;
|
||||
use Modules\User\Models\LogLogin;
|
||||
use Modules\User\Models\Users;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected readonly Users $user
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* get list
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return $this->user->getList();
|
||||
}
|
||||
|
||||
/**
|
||||
* store
|
||||
*
|
||||
* @param Request $request
|
||||
* @return false|mixed
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
return $this->user->storeBy($request->all());
|
||||
}
|
||||
|
||||
/**
|
||||
* show
|
||||
*
|
||||
* @param $id
|
||||
* @return mixed
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
return $this->user->firstBy($id)->makeHidden('password');
|
||||
}
|
||||
|
||||
/**
|
||||
* update
|
||||
*
|
||||
* @param $id
|
||||
* @param Request $request
|
||||
* @return mixed
|
||||
*/
|
||||
public function update($id, Request $request)
|
||||
{
|
||||
return $this->user->updateBy($id, $request->all());
|
||||
}
|
||||
|
||||
/**
|
||||
* destroy
|
||||
*
|
||||
* @param $id
|
||||
* @return bool|null
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
return $this->user->deleteBy($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* enable
|
||||
*
|
||||
* @param $id
|
||||
* @return bool
|
||||
*/
|
||||
public function enable($id)
|
||||
{
|
||||
return $this->user->disOrEnable($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* online user
|
||||
*
|
||||
* @return Authenticatable
|
||||
*/
|
||||
public function online(Request $request)
|
||||
{
|
||||
/* @var Users $user */
|
||||
$user = $this->getLoginUser();
|
||||
|
||||
if ($request->isMethod('post')) {
|
||||
return $user->updateBy($user->id, $request->all());
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* login log
|
||||
* @param LogLogin $logLogin
|
||||
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
* @throws \Psr\Container\ContainerExceptionInterface
|
||||
* @throws \Psr\Container\NotFoundExceptionInterface
|
||||
*/
|
||||
public function loginLog(LogLogin $logLogin)
|
||||
{
|
||||
return $logLogin->getUserLogBy($this->getLoginUser()->email);
|
||||
}
|
||||
}
|
96
modules/User/Listeners/Login.php
Normal file
96
modules/User/Listeners/Login.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\User\Listeners;
|
||||
|
||||
use Catch\Enums\Status;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Stringable;
|
||||
use Modules\User\Events\Login as Event;
|
||||
use Modules\User\Models\LogLogin;
|
||||
use Modules\User\Models\Users;
|
||||
|
||||
class Login
|
||||
{
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param Event $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(Event $event): void
|
||||
{
|
||||
$request = $event->request;
|
||||
|
||||
$this->log($request, (bool) $event->token);
|
||||
|
||||
if ($event->token) {
|
||||
/* @var Users $user */
|
||||
$user = Auth::guard(getGuardName())->user();
|
||||
|
||||
$user->login_ip = $request->ip();
|
||||
$user->login_at = time();
|
||||
$user->remember_token = $event->token;
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* login log
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $isSuccess
|
||||
* @return void
|
||||
*/
|
||||
protected function log(Request $request, int $isSuccess): void
|
||||
{
|
||||
LogLogin::insert([
|
||||
'account' => $request->get('email'),
|
||||
'login_ip' => $request->ip(),
|
||||
'browser' => $this->getBrowserFrom(Str::of($request->userAgent())),
|
||||
'platform' => $this->getPlatformFrom(Str::of($request->userAgent())),
|
||||
'login_at' => time(),
|
||||
'status' => $isSuccess ? Status::Enable : Status::Disable
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get platform
|
||||
*
|
||||
* @param Stringable $userAgent
|
||||
* @return string
|
||||
*/
|
||||
protected function getBrowserFrom(Stringable $userAgent): string
|
||||
{
|
||||
return match (true) {
|
||||
$userAgent->contains('MSIE', true) => 'IE',
|
||||
$userAgent->contains('Firefox', true) => 'Firefox',
|
||||
$userAgent->contains('Chrome', true) => 'Chrome',
|
||||
$userAgent->contains('Opera', true) => 'Opera',
|
||||
$userAgent->contains('Safari', true) => 'Safari',
|
||||
default => 'unknown'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get os name
|
||||
*
|
||||
* @param Stringable $userAgent
|
||||
* @return string
|
||||
*/
|
||||
protected function getPlatformFrom(Stringable $userAgent): string
|
||||
{
|
||||
return match (true) {
|
||||
$userAgent->contains('win', true) => 'Windows',
|
||||
$userAgent->contains('mac', true) => 'Mac OS',
|
||||
$userAgent->contains('linux', true) => 'Linux',
|
||||
$userAgent->contains('iphone', true) => 'iphone',
|
||||
$userAgent->contains('android', true) => 'Android',
|
||||
default => 'unknown'
|
||||
};
|
||||
}
|
||||
}
|
37
modules/User/Models/LogLogin.php
Normal file
37
modules/User/Models/LogLogin.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\User\Models;
|
||||
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class LogLogin extends Model
|
||||
{
|
||||
protected $table = 'log_login';
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'id', 'account', 'login_ip', 'browser', 'platform', 'login_at', 'status',
|
||||
];
|
||||
|
||||
|
||||
protected $casts = [
|
||||
'login_at' => 'datetime:Y-m-d H:i'
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $email
|
||||
* @return LengthAwarePaginator
|
||||
* @throws ContainerExceptionInterface
|
||||
* @throws NotFoundExceptionInterface
|
||||
*/
|
||||
public function getUserLogBy(string $email): LengthAwarePaginator
|
||||
{
|
||||
return self::query()->where('account', $email)
|
||||
->paginate(request()->get('limit', 10));
|
||||
}
|
||||
}
|
98
modules/User/Models/Users.php
Normal file
98
modules/User/Models/Users.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\User\Models;
|
||||
|
||||
use Catch\Base\CatchModel as Model;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Tymon\JWTAuth\Contracts\JWTSubject;
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $username
|
||||
* @property string $email
|
||||
* @property string $avatar
|
||||
* @property string $password
|
||||
* @property int $creator_id
|
||||
* @property int $status
|
||||
* @property string $login_ip
|
||||
* @property int $login_at
|
||||
* @property int $created_at
|
||||
* @property int $updated_at
|
||||
* @property string $remember_token
|
||||
*/
|
||||
class Users extends Model implements AuthenticatableContract, JWTSubject
|
||||
{
|
||||
use Authenticatable;
|
||||
|
||||
protected $fillable = [
|
||||
'id', 'username', 'email', 'avatar', 'password', 'remember_token', 'creator_id', 'status', 'login_ip', 'login_at', 'created_at', 'updated_at', 'deleted_at'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
public array $searchable = [
|
||||
'username' => 'like',
|
||||
'email' => 'like',
|
||||
'status' => '='
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'users';
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
protected array $form = ['username', 'email', 'password'];
|
||||
|
||||
/**
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getJWTIdentifier(): mixed
|
||||
{
|
||||
return $this->getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a key value array, containing any custom claims to be added to the JWT.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getJWTCustomClaims(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* password
|
||||
*
|
||||
* @return Attribute
|
||||
*/
|
||||
protected function password(): Attribute
|
||||
{
|
||||
return new Attribute(
|
||||
// get: fn($value) => '',
|
||||
set: fn ($value) => bcrypt($value),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* update
|
||||
* @param $id
|
||||
* @param array $data
|
||||
* @return mixed
|
||||
*/
|
||||
public function updateBy($id, array $data): mixed
|
||||
{
|
||||
if (isset($data['password']) && ! $data['password']) {
|
||||
unset($data['password']);
|
||||
}
|
||||
|
||||
return parent::updateBy($id, $data);
|
||||
}
|
||||
}
|
32
modules/User/Providers/UserServiceProvider.php
Normal file
32
modules/User/Providers/UserServiceProvider.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\User\Providers;
|
||||
|
||||
use Catch\CatchAdmin;
|
||||
use Catch\Providers\CatchModuleServiceProvider;
|
||||
use Modules\User\Events\Login;
|
||||
use Modules\User\Listeners\Login as LoginListener;
|
||||
|
||||
class UserServiceProvider extends CatchModuleServiceProvider
|
||||
{
|
||||
protected array $events = [
|
||||
Login::class => LoginListener::class
|
||||
];
|
||||
|
||||
/**
|
||||
* route path
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function routePath(): string|array
|
||||
{
|
||||
// TODO: Implement path() method.
|
||||
return CatchAdmin::getModuleRoutePath('user');
|
||||
}
|
||||
|
||||
|
||||
public function registerEvents(array $events): void
|
||||
{
|
||||
parent::registerEvents($events); // TODO: Change the autogenerated stub
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
<?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(): void
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
|
||||
$table->string('username')->comment('昵称');
|
||||
|
||||
$table->string('password')->comment('密码');
|
||||
|
||||
$table->string('email')->comment('邮箱');
|
||||
|
||||
$table->string('avatar')->comment('头像');
|
||||
|
||||
$table->string('remember_token', 1000)->comment('token');
|
||||
|
||||
$table->integer('creator_id');
|
||||
|
||||
$table->status();
|
||||
|
||||
$table->string('login_ip')->comment('登录IP');
|
||||
|
||||
$table->integer('login_at')->comment('登录时间');
|
||||
|
||||
$table->createdAt();
|
||||
|
||||
$table->updatedAt();
|
||||
|
||||
$table->deletedAt();
|
||||
|
||||
$table->comment('用户表');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
}
|
||||
};
|
@@ -0,0 +1,40 @@
|
||||
<?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(): void
|
||||
{
|
||||
Schema::create('log_login', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
|
||||
$table->string('account')->comment('登录账户');
|
||||
|
||||
$table->string('login_ip')->comment('登录的IP');
|
||||
|
||||
$table->string('browser')->comment('浏览器');
|
||||
|
||||
$table->string('platform')->comment('平台');
|
||||
|
||||
$table->integer('login_at')->comment('平台');
|
||||
|
||||
$table->status();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
}
|
||||
};
|
15
modules/User/route.php
Normal file
15
modules/User/route.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Modules\User\Http\Controllers\AuthController;
|
||||
use Modules\User\Http\Controllers\UserController;
|
||||
|
||||
// login route
|
||||
Route::post('login', [AuthController::class, 'login'])->withoutMiddleware(config('catch.route.middlewares'));
|
||||
Route::post('logout', [AuthController::class, 'logout']);
|
||||
|
||||
// users route
|
||||
Route::apiResource('users', UserController::class);
|
||||
Route::put('users/enable/{id}', [UserController::class, 'enable']);
|
||||
Route::match(['post', 'get'], 'user/online', [UserController::class, 'online']);
|
||||
Route::get('user/login/log', [UserController::class, 'loginLog']);
|
26
modules/User/views/router.ts
Normal file
26
modules/User/views/router.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
// @ts-ignore
|
||||
const router: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/users',
|
||||
component: () => import('/admin/layout/index.vue'),
|
||||
meta: { title: '用户管理', icon: 'user' },
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'users',
|
||||
meta: { title: '账号管理', icon: 'home' },
|
||||
component: () => import('./user/index.vue'),
|
||||
},
|
||||
{
|
||||
path: 'center',
|
||||
name: 'center',
|
||||
meta: { title: '个人中心', icon: 'home' },
|
||||
component: () => import('./user/center.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export default router
|
44
modules/User/views/user/center.vue
Normal file
44
modules/User/views/user/center.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div class="flex flex-col sm:flex-row dark:bg-regal-dark w-full">
|
||||
<el-card shadow="never" class="w-full sm:w-[35rem]">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>个人资料</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="w-full">
|
||||
<Profile />
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-tabs v-model="activeName" class="pl-3 pr-3 bg-white dark:bg-regal-dark mt-2 sm:mt-0 w-full ml-2">
|
||||
<el-tab-pane label="登录日志" name="login_log">
|
||||
<LoginLog />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="操作日志" name="operation_log">
|
||||
<OperateLog />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import Profile from './components/profile.vue'
|
||||
import LoginLog from './components/loginLog.vue'
|
||||
import OperateLog from './components/operateLog.vue'
|
||||
|
||||
const activeName = ref('login_log')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-tabs {
|
||||
--el-tabs-header-height: 62px !important;
|
||||
}
|
||||
|
||||
.el-tabs .el-tabs__item {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
</style>
|
45
modules/User/views/user/components/loginLog.vue
Normal file
45
modules/User/views/user/components/loginLog.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<el-table :data="tableData" class="mt-3" v-loading="loading">
|
||||
<el-table-column prop="account" label="账户" width="150px" />
|
||||
<el-table-column prop="browser" label="浏览器" width="100px" />
|
||||
<el-table-column prop="platform" label="平台" width="100px" />
|
||||
<el-table-column prop="login_ip" label="IP" width="120px" />
|
||||
<el-table-column prop="status" label="状态" width="100px">
|
||||
<template #default="scope">
|
||||
<el-tag type="success" v-if="scope.row.status === 1">成功</el-tag>
|
||||
<el-tag type="danger" v-else>失败</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="login_at" label="登录时间" />
|
||||
</el-table>
|
||||
|
||||
<div class="pt-2 pb-2 flex justify-end">
|
||||
<el-pagination
|
||||
background
|
||||
v-if="total > query.limit"
|
||||
layout="total,sizes,prev, pager,next"
|
||||
:current-page="query.page"
|
||||
:page-size="query.limit"
|
||||
@current-change="changePage"
|
||||
@size-change="changeLimit"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useGetList } from '/admin/composables/curd/useGetList'
|
||||
|
||||
const api = 'user/login/log'
|
||||
|
||||
const { data, query, search, changePage, changeLimit, loading } = useGetList(api)
|
||||
|
||||
onMounted(() => search())
|
||||
|
||||
const tableData = computed(() => data.value?.data)
|
||||
const total = computed(() => data.value?.total)
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
43
modules/User/views/user/components/operateLog.vue
Normal file
43
modules/User/views/user/components/operateLog.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<el-table :data="tableData" class="mt-3" v-loading="loading">
|
||||
<el-table-column prop="username" label="用户名" width="180" />
|
||||
<el-table-column prop="avatar" label="头像" width="180" />
|
||||
<el-table-column prop="email" label="邮箱" />
|
||||
<el-table-column prop="status" label="状态">
|
||||
<template #default="scope">
|
||||
<Status v-model="scope.row.status" :id="scope.row.id" :api="api" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" />
|
||||
</el-table>
|
||||
|
||||
<div class="pt-2 pb-2 flex justify-end">
|
||||
<el-pagination
|
||||
background
|
||||
v-if="total > query.limit"
|
||||
layout="total,sizes,prev, pager,next"
|
||||
:current-page="query.page"
|
||||
:page-size="query.limit"
|
||||
@current-change="changePage"
|
||||
@size-change="changeLimit"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { useGetList } from '/admin/composables/curd/useGetList'
|
||||
|
||||
const api = 'users'
|
||||
|
||||
const { data, query, search, reset, changePage, changeLimit, loading } = useGetList(api)
|
||||
|
||||
onMounted(() => search())
|
||||
|
||||
const tableData = computed(() => data.value?.data)
|
||||
const total = computed(() => data.value?.total)
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
88
modules/User/views/user/components/profile.vue
Normal file
88
modules/User/views/user/components/profile.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<el-form :model="profile" ref="form" v-loading="loading" label-position="top">
|
||||
<el-upload
|
||||
class="w-28 h-28 rounded-full mx-auto"
|
||||
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
|
||||
:show-file-list="false"
|
||||
:on-success="handleAvatarSuccess"
|
||||
:before-upload="beforeAvatarUpload"
|
||||
>
|
||||
<img src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" class="h-28 rounded-full" />
|
||||
</el-upload>
|
||||
<el-form-item
|
||||
label="昵称"
|
||||
prop="username"
|
||||
class="mt-2"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '昵称必须填写',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<el-input v-model="profile.username" placeholder="请填写昵称" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="邮箱"
|
||||
prop="email"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '邮箱必须填写',
|
||||
},
|
||||
{
|
||||
type: 'email',
|
||||
message: '邮箱格式不正确',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<el-input v-model="profile.email" placeholder="请填写邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="密码"
|
||||
prop="password"
|
||||
:rules="[
|
||||
{
|
||||
pattern: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20}$/,
|
||||
message: '必须包含大小写字母和数字的组合,可以使用特殊字符,长度在6-20之间',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<el-input v-model="profile.password" type="password" show-password placeholder="请输入密码" />
|
||||
</el-form-item>
|
||||
<div class="flex justify-center">
|
||||
<el-button type="primary" @click="submitForm(form)">{{ $t('system.update') }}</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, unref } from 'vue'
|
||||
import { useCreate } from '/admin/composables/curd/useCreate'
|
||||
import http from '/admin/support/http'
|
||||
|
||||
interface profile {
|
||||
avatar: string
|
||||
username: string
|
||||
email: string
|
||||
password: string
|
||||
}
|
||||
|
||||
const profile = ref<profile>(
|
||||
Object.assign({
|
||||
avatar: '',
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
}),
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
http.get('user/online').then(r => {
|
||||
profile.value.username = r.data.data.username
|
||||
profile.value.avatar = r.data.data.avatar
|
||||
profile.value.email = r.data.data.email
|
||||
})
|
||||
})
|
||||
const { form, loading, submitForm } = useCreate('user/online', null, profile)
|
||||
</script>
|
82
modules/User/views/user/create.vue
Normal file
82
modules/User/views/user/create.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<el-form :model="formData" label-width="120px" ref="form" v-loading="loading" class="pr-4">
|
||||
<el-form-item
|
||||
label="昵称"
|
||||
prop="username"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '昵称必须填写',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<el-input v-model="formData.username" placeholder="请填写昵称" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="邮箱"
|
||||
prop="email"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: '邮箱必须填写',
|
||||
},
|
||||
{
|
||||
type: 'email',
|
||||
message: '邮箱格式不正确',
|
||||
},
|
||||
]"
|
||||
>
|
||||
<el-input v-model="formData.email" placeholder="请填写邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password" :rules="passwordRules">
|
||||
<el-input v-model="formData.password" type="password" show-password placeholder="请输入密码" />
|
||||
</el-form-item>
|
||||
<div class="flex justify-end">
|
||||
<el-button type="primary" @click="submitForm(form)">{{ $t('system.confirm') }}</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useCreate } from '/admin/composables/curd/useCreate'
|
||||
import { useShow } from '/admin/composables/curd/useShow'
|
||||
|
||||
import { onMounted, watch, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
primary: String | Number,
|
||||
api: String,
|
||||
})
|
||||
|
||||
const passwordRules = [
|
||||
{
|
||||
required: true,
|
||||
message: '密码必须填写',
|
||||
},
|
||||
{
|
||||
pattern: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{6,20}$/,
|
||||
message: '必须包含大小写字母和数字的组合,可以使用特殊字符,长度在6-20之间',
|
||||
},
|
||||
]
|
||||
|
||||
if (props.primary) {
|
||||
passwordRules.shift()
|
||||
}
|
||||
|
||||
const { formData, form, loading, submitForm, isClose } = useCreate(props.api, props.primary)
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
watch(isClose, function (value) {
|
||||
if (value) {
|
||||
emit('close')
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (props.primary) {
|
||||
useShow(props.api, props.primary).then(r => {
|
||||
formData.value = r.data
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
104
modules/User/views/user/index.vue
Normal file
104
modules/User/views/user/index.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="w-full min-h-0 bg-white dark:bg-regal-dark pl-5 pt-5 pr-5 rounded-lg">
|
||||
<el-form :inline="true">
|
||||
<el-form-item label="用户名">
|
||||
<el-input v-model="query.username" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱">
|
||||
<el-input v-model="query.email" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<Select v-model="query.status" clearable api="status" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="search()">
|
||||
<Icon name="magnifying-glass" class="w-4 mr-1 -ml-1" />
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="reset()">
|
||||
<Icon name="arrow-path" class="w-4 mr-1 -ml-1" />
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="pl-2 pr-2 bg-white dark:bg-regal-dark rounded-lg mt-4">
|
||||
<div class="pt-5 pl-2">
|
||||
<Add @click="show(null)" />
|
||||
</div>
|
||||
<el-table :data="tableData" class="mt-3" v-loading="loading">
|
||||
<el-table-column prop="username" label="用户名" width="180" />
|
||||
<el-table-column prop="avatar" label="头像" width="180" />
|
||||
<el-table-column prop="email" label="邮箱" />
|
||||
<el-table-column prop="status" label="状态">
|
||||
<template #default="scope">
|
||||
<Status v-model="scope.row.status" :id="scope.row.id" :api="api" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="创建时间" />
|
||||
<el-table-column label="操作" width="200">
|
||||
<template #default="scope">
|
||||
<Update @click="show(scope.row.id)" />
|
||||
<Destroy @click="destroy(api, scope.row.id)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pt-2 pb-2 flex justify-end">
|
||||
<el-pagination
|
||||
background
|
||||
layout="total,sizes,prev, pager,next"
|
||||
:current-page="query.page"
|
||||
:page-size="query.limit"
|
||||
@current-change="changePage"
|
||||
@size-change="changeLimit"
|
||||
:total="total"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog v-model="visible" :title="title" destroy-on-close>
|
||||
<Create @close="close" :primary="id" :api="api" />
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import Create from './create.vue'
|
||||
import { useGetList } from '/admin/composables/curd/useGetList'
|
||||
import { useDestroy } from '/admin/composables/curd/useDestroy'
|
||||
import { useEnabled } from '/admin/composables/curd/useEnabled'
|
||||
import { t } from '/admin/support/helper'
|
||||
|
||||
const visible = ref<boolean>(false)
|
||||
const id = ref(null)
|
||||
const api = 'users'
|
||||
const title = ref<string>('')
|
||||
|
||||
const { data, query, search, reset, changePage, changeLimit, loading } = useGetList(api)
|
||||
const { destroy, isDeleted } = useDestroy()
|
||||
|
||||
onMounted(() => search())
|
||||
|
||||
const tableData = computed(() => data.value?.data)
|
||||
const total = computed(() => data.value?.total)
|
||||
|
||||
const close = () => {
|
||||
visible.value = false
|
||||
reset()
|
||||
}
|
||||
|
||||
const show = primary => {
|
||||
title.value = primary ? t('system.edit') : t('system.add')
|
||||
id.value = primary
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
watch(isDeleted, function () {
|
||||
isDeleted.value = false
|
||||
reset()
|
||||
})
|
||||
</script>
|
Reference in New Issue
Block a user