mirror of
https://github.com/cedar2025/Xboard.git
synced 2025-02-12 19:48:13 -05:00
feat: add user deletion functionality and fix known issues
This commit is contained in:
parent
1ae8deca99
commit
b2e7ed44f3
@ -4,6 +4,7 @@ namespace App\Http\Controllers\V1\User;
|
|||||||
|
|
||||||
use App\Exceptions\ApiException;
|
use App\Exceptions\ApiException;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Resources\CouponResource;
|
||||||
use App\Services\CouponService;
|
use App\Services\CouponService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
@ -11,19 +12,14 @@ class CouponController extends Controller
|
|||||||
{
|
{
|
||||||
public function check(Request $request)
|
public function check(Request $request)
|
||||||
{
|
{
|
||||||
// $request->validate([
|
|
||||||
// 'code' => 'required|string',
|
|
||||||
// 'plan_id' => 'required|integer',
|
|
||||||
// 'period' => 'nullable|string',
|
|
||||||
// ]);
|
|
||||||
if (empty($request->input('code'))) {
|
if (empty($request->input('code'))) {
|
||||||
return $this->fail([422,__('Coupon cannot be empty')]);
|
return $this->fail([422, __('Coupon cannot be empty')]);
|
||||||
}
|
}
|
||||||
$couponService = new CouponService($request->input('code'));
|
$couponService = new CouponService($request->input('code'));
|
||||||
$couponService->setPlanId($request->input('plan_id'));
|
$couponService->setPlanId($request->input('plan_id'));
|
||||||
$couponService->setUserId($request->user()->id);
|
$couponService->setUserId($request->user()->id);
|
||||||
$couponService->setPeriod($request->input('period'));
|
$couponService->setPeriod($request->input('period'));
|
||||||
$couponService->check();
|
$couponService->check();
|
||||||
return $this->success($couponService->getCoupon());
|
return $this->success(CouponResource::make($couponService->getCoupon()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ use App\Services\AuthService;
|
|||||||
use App\Utils\Helper;
|
use App\Utils\Helper;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class UserController extends Controller
|
class UserController extends Controller
|
||||||
@ -86,11 +87,11 @@ class UserController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
[$operator, $filterValue] = explode(':', $value, 2);
|
[$operator, $filterValue] = explode(':', $value, 2);
|
||||||
|
|
||||||
// Convert numeric strings to appropriate type
|
// Convert numeric strings to appropriate type
|
||||||
if (is_numeric($filterValue)) {
|
if (is_numeric($filterValue)) {
|
||||||
$filterValue = strpos($filterValue, '.') !== false
|
$filterValue = strpos($filterValue, '.') !== false
|
||||||
? (float) $filterValue
|
? (float) $filterValue
|
||||||
: (int) $filterValue;
|
: (int) $filterValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,12 +150,12 @@ class UserController extends Controller
|
|||||||
{
|
{
|
||||||
$current = $request->input('current', 1);
|
$current = $request->input('current', 1);
|
||||||
$pageSize = $request->input('pageSize', 10);
|
$pageSize = $request->input('pageSize', 10);
|
||||||
|
|
||||||
$userModel = User::with(['plan:id,name', 'invite_user:id,email', 'group:id,name'])
|
$userModel = User::with(['plan:id,name', 'invite_user:id,email', 'group:id,name'])
|
||||||
->select(DB::raw('*, (u+d) as total_used'));
|
->select(DB::raw('*, (u+d) as total_used'));
|
||||||
|
|
||||||
$this->applyFiltersAndSorts($request, $userModel);
|
$this->applyFiltersAndSorts($request, $userModel);
|
||||||
|
|
||||||
$users = $userModel->orderBy('id', 'desc')
|
$users = $userModel->orderBy('id', 'desc')
|
||||||
->paginate($pageSize, ['*'], 'page', $current);
|
->paginate($pageSize, ['*'], 'page', $current);
|
||||||
|
|
||||||
@ -397,4 +398,35 @@ class UserController extends Controller
|
|||||||
|
|
||||||
return $this->success(true);
|
return $this->success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户及其关联数据
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function destroy(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'id' => 'required|exists:App\Models\User,id'
|
||||||
|
], [
|
||||||
|
'id.required' => '用户ID不能为空',
|
||||||
|
'id.exists' => '用户不存在'
|
||||||
|
]);
|
||||||
|
$user = User::find($request->input('id'));
|
||||||
|
try {
|
||||||
|
DB::beginTransaction();
|
||||||
|
$user->orders()->delete();
|
||||||
|
$user->codes()->delete();
|
||||||
|
$user->stat()->delete();
|
||||||
|
$user->tickets()->delete();
|
||||||
|
$user->delete();
|
||||||
|
DB::commit();
|
||||||
|
return $this->success(true);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
\Log::error($e);
|
||||||
|
return $this->fail([500, '删除失败']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
34
app/Http/Resources/CouponResource.php
Normal file
34
app/Http/Resources/CouponResource.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券资源类
|
||||||
|
*
|
||||||
|
* @property array|null $limit_plan_ids 限制可用的套餐ID列表
|
||||||
|
*/
|
||||||
|
class CouponResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 将资源转换为数组
|
||||||
|
*
|
||||||
|
* @param Request $request 请求实例
|
||||||
|
* @return array<string, mixed> 转换后的数组
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
...$this->resource->toArray(),
|
||||||
|
'limit_plan_ids' => $this->when(
|
||||||
|
!empty($this->limit_plan_ids),
|
||||||
|
fn() => collect($this->limit_plan_ids)
|
||||||
|
->map(fn(mixed $id): string => (string) $id)
|
||||||
|
->values()
|
||||||
|
->all()
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -108,6 +108,7 @@ class AdminRoute
|
|||||||
$router->post('/ban', [UserController::class, 'ban']);
|
$router->post('/ban', [UserController::class, 'ban']);
|
||||||
$router->post('/resetSecret', [UserController::class, 'resetSecret']);
|
$router->post('/resetSecret', [UserController::class, 'resetSecret']);
|
||||||
$router->post('/setInviteUser', [UserController::class, 'setInviteUser']);
|
$router->post('/setInviteUser', [UserController::class, 'setInviteUser']);
|
||||||
|
$router->post('/destroy', [UserController::class, 'destroy']);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stat
|
// Stat
|
||||||
|
@ -24,17 +24,4 @@ class Coupon extends Model
|
|||||||
})->toArray();
|
})->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getLimitPlanIdsAttribute($value)
|
|
||||||
{
|
|
||||||
$planIds = json_decode($value, true);
|
|
||||||
|
|
||||||
if (blank($planIds)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return collect($planIds)
|
|
||||||
->map(fn($id) => (string) $id)
|
|
||||||
->values()
|
|
||||||
->all();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,16 @@ class User extends Authenticatable
|
|||||||
return $this->hasMany(InviteCode::class, 'user_id', 'id');
|
return $this->hasMany(InviteCode::class, 'user_id', 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function orders()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Order::class, 'user_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stat()
|
||||||
|
{
|
||||||
|
return $this->hasMany(StatUser::class, 'user_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
// 关联工单列表
|
// 关联工单列表
|
||||||
public function tickets()
|
public function tickets()
|
||||||
{
|
{
|
||||||
|
14
public/assets/admin/assets/index.js
vendored
14
public/assets/admin/assets/index.js
vendored
File diff suppressed because one or more lines are too long
12
public/assets/admin/locales/en-US.js
vendored
12
public/assets/admin/locales/en-US.js
vendored
@ -815,7 +815,11 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
|||||||
"success": "Success",
|
"success": "Success",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"delete": "Delete",
|
"confirm": "Confirm",
|
||||||
|
"delete": {
|
||||||
|
"success": "Deleted successfully",
|
||||||
|
"failed": "Failed to delete"
|
||||||
|
},
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"view": "View",
|
"view": "View",
|
||||||
"toggleNavigation": "Toggle Navigation",
|
"toggleNavigation": "Toggle Navigation",
|
||||||
@ -832,6 +836,7 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
|||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"copy": {
|
"copy": {
|
||||||
"success": "Copied successfully",
|
"success": "Copied successfully",
|
||||||
|
"failed": "Failed to copy",
|
||||||
"error": "Copy failed",
|
"error": "Copy failed",
|
||||||
"errorLog": "Error copying to clipboard"
|
"errorLog": "Error copying to clipboard"
|
||||||
},
|
},
|
||||||
@ -1740,7 +1745,10 @@ window.XBOARD_TRANSLATIONS['en-US'] = {
|
|||||||
"reset_secret": "Reset UUID & URL",
|
"reset_secret": "Reset UUID & URL",
|
||||||
"orders": "Orders",
|
"orders": "Orders",
|
||||||
"invites": "Invites",
|
"invites": "Invites",
|
||||||
"traffic_records": "Traffic Records"
|
"traffic_records": "Traffic Records",
|
||||||
|
"delete": "Delete",
|
||||||
|
"delete_confirm_title": "Confirm Delete User",
|
||||||
|
"delete_confirm_description": "This action will permanently delete user {{email}} and all associated data, including orders, coupons, traffic records, and support tickets. This action cannot be undone. Do you want to continue?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
|
20
public/assets/admin/locales/ko-KR.js
vendored
20
public/assets/admin/locales/ko-KR.js
vendored
@ -815,7 +815,11 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
|||||||
"success": "성공",
|
"success": "성공",
|
||||||
"save": "저장",
|
"save": "저장",
|
||||||
"cancel": "취소",
|
"cancel": "취소",
|
||||||
"delete": "삭제",
|
"confirm": "확인",
|
||||||
|
"delete": {
|
||||||
|
"success": "삭제되었습니다",
|
||||||
|
"failed": "삭제에 실패했습니다"
|
||||||
|
},
|
||||||
"edit": "편집",
|
"edit": "편집",
|
||||||
"view": "보기",
|
"view": "보기",
|
||||||
"toggleNavigation": "네비게이션 전환",
|
"toggleNavigation": "네비게이션 전환",
|
||||||
@ -831,9 +835,8 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
|||||||
"settings": "설정",
|
"settings": "설정",
|
||||||
"logout": "로그아웃",
|
"logout": "로그아웃",
|
||||||
"copy": {
|
"copy": {
|
||||||
"success": "복사 성공",
|
"success": "복사되었습니다",
|
||||||
"error": "복사 실패",
|
"failed": "복사에 실패했습니다"
|
||||||
"errorLog": "클립보드에 복사하는 중 오류 발생"
|
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"noData": "데이터가 없습니다",
|
"noData": "데이터가 없습니다",
|
||||||
@ -1692,9 +1695,12 @@ window.XBOARD_TRANSLATIONS['ko-KR'] = {
|
|||||||
"assign_order": "주문 할당",
|
"assign_order": "주문 할당",
|
||||||
"copy_url": "구독 URL 복사",
|
"copy_url": "구독 URL 복사",
|
||||||
"reset_secret": "UUID 및 URL 재설정",
|
"reset_secret": "UUID 및 URL 재설정",
|
||||||
"orders": "주문",
|
"orders": "주문 내역",
|
||||||
"invites": "초대",
|
"invites": "초대 내역",
|
||||||
"traffic_records": "트래픽 기록"
|
"traffic_records": "트래픽 기록",
|
||||||
|
"delete": "삭제",
|
||||||
|
"delete_confirm_title": "사용자 삭제 확인",
|
||||||
|
"delete_confirm_description": "이 작업은 사용자 {{email}}와 관련된 모든 데이터(주문, 쿠폰, 트래픽 기록, 지원 티켓 등)를 영구적으로 삭제합니다. 이 작업은 취소할 수 없습니다. 계속하시겠습니까?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
|
12
public/assets/admin/locales/zh-CN.js
vendored
12
public/assets/admin/locales/zh-CN.js
vendored
@ -820,7 +820,11 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"success": "成功",
|
"success": "成功",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"delete": "删除",
|
"confirm": "确认",
|
||||||
|
"delete": {
|
||||||
|
"success": "删除成功",
|
||||||
|
"failed": "删除失败"
|
||||||
|
},
|
||||||
"edit": "编辑",
|
"edit": "编辑",
|
||||||
"view": "查看",
|
"view": "查看",
|
||||||
"toggleNavigation": "切换导航",
|
"toggleNavigation": "切换导航",
|
||||||
@ -837,6 +841,7 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"logout": "退出登录",
|
"logout": "退出登录",
|
||||||
"copy": {
|
"copy": {
|
||||||
"success": "复制成功",
|
"success": "复制成功",
|
||||||
|
"failed": "复制失败",
|
||||||
"error": "复制失败",
|
"error": "复制失败",
|
||||||
"errorLog": "复制到剪贴板时出错"
|
"errorLog": "复制到剪贴板时出错"
|
||||||
},
|
},
|
||||||
@ -1707,7 +1712,10 @@ window.XBOARD_TRANSLATIONS['zh-CN'] = {
|
|||||||
"reset_secret": "重置UUID及订阅URL",
|
"reset_secret": "重置UUID及订阅URL",
|
||||||
"orders": "TA的订单",
|
"orders": "TA的订单",
|
||||||
"invites": "TA的邀请",
|
"invites": "TA的邀请",
|
||||||
"traffic_records": "TA的流量记录"
|
"traffic_records": "TA的流量记录",
|
||||||
|
"delete": "删除",
|
||||||
|
"delete_confirm_title": "确认删除用户",
|
||||||
|
"delete_confirm_description": "此操作将永久删除用户 {{email}} 及其所有相关数据,包括订单、优惠码、流量记录、工单记录等信息。删除后无法恢复,是否继续?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
|
Loading…
Reference in New Issue
Block a user