refactor: 重构后端目录结构和命名规范
- 将后端目录名从大写改为小写 (Controller->controller 等) - 将命名空间从 App\ 改为 app\ (符合 webman 官方规范) - 移除不必要的 bootstrap 目录 - 完善出差审批业务功能: - 添加删除草稿功能 - 添加审批操作功能(同意/拒绝) - 完善审批详情页,显示审批人和审批记录 - 前端优化: - 调整列表页行高 - 隐藏底部导航栏 - 仪表盘只显示3个快捷入口 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Bootstrap;
|
||||
|
||||
use App\Util\Db;
|
||||
use Webman\Bootstrap;
|
||||
|
||||
class Database implements Bootstrap
|
||||
{
|
||||
public static function start(?\Workerman\Worker $worker): void
|
||||
{
|
||||
// 初始化数据库连接
|
||||
Db::init();
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Middleware;
|
||||
|
||||
use App\Util\JwtUtil;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
class AuthMiddleware
|
||||
{
|
||||
public function process(Request $request, callable $next): Response
|
||||
{
|
||||
// 从请求头获取 token
|
||||
$token = $request->header('Authorization');
|
||||
$token = str_replace('Bearer ', '', $token);
|
||||
|
||||
// 如果没有 token,返回 401
|
||||
if (!$token) {
|
||||
return json([
|
||||
'code' => 401,
|
||||
'message' => '未登录',
|
||||
'data' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
// 验证 token
|
||||
$payload = JwtUtil::decode($token);
|
||||
if (!$payload) {
|
||||
return json([
|
||||
'code' => 401,
|
||||
'message' => '登录已过期',
|
||||
'data' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
// 将用户ID注入到请求中
|
||||
$request->userId = (int) ($payload['user_id'] ?? 0);
|
||||
$request->companyId = (int) ($payload['company_id'] ?? 0);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
namespace app\controller;
|
||||
|
||||
use App\Service\ApprovalService;
|
||||
use app\service\ApprovalService;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
namespace app\controller;
|
||||
|
||||
use App\Service\FeishuService;
|
||||
use App\Util\JwtUtil;
|
||||
use App\Model\User;
|
||||
use App\Model\Company;
|
||||
use app\service\FeishuService;
|
||||
use app\util\JwtUtil;
|
||||
use app\model\User;
|
||||
use app\model\Company;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
namespace app\controller;
|
||||
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
+45
-2
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
namespace app\controller;
|
||||
|
||||
use App\Service\BusinessTripService;
|
||||
use app\service\BusinessTripService;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
@@ -144,6 +144,49 @@ class BusinessTripController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除草稿
|
||||
*/
|
||||
public function deleteDraft(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$id = (int) $this->getRouteParam($request, 'id');
|
||||
if (!$id) {
|
||||
return $this->error('参数错误');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->service->deleteDraft($userId, $id);
|
||||
return $this->success(null, '删除成功');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批操作
|
||||
*/
|
||||
public function approve(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$id = (int) $this->getRouteParam($request, 'id');
|
||||
if (!$id) {
|
||||
return $this->error('参数错误');
|
||||
}
|
||||
|
||||
$data = $request->post();
|
||||
$data = $data ?? [];
|
||||
$action = $data['action'] ?? 'approve'; // approve 或 reject
|
||||
$comment = $data['comment'] ?? '';
|
||||
|
||||
try {
|
||||
$result = $this->service->approve($userId, $id, $action, $comment);
|
||||
return $this->success($result, $action === 'approve' ? '已同意' : '已拒绝');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消出差申请
|
||||
*/
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
namespace app\controller;
|
||||
|
||||
use App\Model\User;
|
||||
use app\model\User;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
@@ -5,31 +5,42 @@ namespace app\middleware;
|
||||
use app\util\JwtUtil;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
use support\Log;
|
||||
|
||||
class AuthMiddleware
|
||||
{
|
||||
public function process(Request $request, callable $handler): Response
|
||||
public function process(Request $request, callable $next): Response
|
||||
{
|
||||
// 获取 Token
|
||||
// 从请求头获取 token
|
||||
$token = $request->header('Authorization');
|
||||
$token = str_replace('Bearer ', '', $token);
|
||||
|
||||
if (!$token || !JwtUtil::verify($token)) {
|
||||
// 移除 "Bearer " 前缀(如果存在)
|
||||
if ($token && str_starts_with($token, 'Bearer ')) {
|
||||
$token = substr($token, 7);
|
||||
}
|
||||
|
||||
// 如果没有 token,返回 401
|
||||
if (!$token) {
|
||||
return json([
|
||||
'code' => 401,
|
||||
'message' => 'Unauthorized',
|
||||
'message' => '未登录',
|
||||
'data' => null,
|
||||
], 401);
|
||||
]);
|
||||
}
|
||||
|
||||
// 解析 Token 并设置用户信息到请求
|
||||
// 验证 token
|
||||
$payload = JwtUtil::decode($token);
|
||||
if ($payload) {
|
||||
$request->userId = $payload['user_id'] ?? null;
|
||||
$request->companyId = $payload['company_id'] ?? null;
|
||||
if (!$payload) {
|
||||
return json([
|
||||
'code' => 401,
|
||||
'message' => '登录已过期',
|
||||
'data' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
return $handler($request);
|
||||
// 将用户ID注入到请求中
|
||||
$request->userId = (int) ($payload['user_id'] ?? 0);
|
||||
$request->companyId = (int) ($payload['company_id'] ?? 0);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
namespace app\model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
namespace app\model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
namespace app\model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
namespace app\model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
namespace app\model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
namespace app\model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
namespace app\model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
namespace app\model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
namespace app\model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
namespace app\service;
|
||||
|
||||
use App\Model\ApprovalFlow;
|
||||
use App\Model\ApprovalNode;
|
||||
use App\Model\ApprovalOrder;
|
||||
use App\Model\ApprovalRecord;
|
||||
use App\Model\User;
|
||||
use App\Util\Db;
|
||||
use app\model\ApprovalFlow;
|
||||
use app\model\ApprovalNode;
|
||||
use app\model\ApprovalOrder;
|
||||
use app\model\ApprovalRecord;
|
||||
use app\model\User;
|
||||
use app\util\Db;
|
||||
use Illuminate\Database\Connection;
|
||||
|
||||
class ApprovalService
|
||||
+172
-13
@@ -1,14 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
namespace app\service;
|
||||
|
||||
use App\Model\BusinessTrip;
|
||||
use App\Model\BusinessTripItinerary;
|
||||
use App\Model\ApprovalOrder;
|
||||
use App\Model\ApprovalFlow;
|
||||
use App\Model\ApprovalRecord;
|
||||
use App\Model\User;
|
||||
use App\Util\Db;
|
||||
use app\model\BusinessTrip;
|
||||
use app\model\BusinessTripItinerary;
|
||||
use app\model\ApprovalOrder;
|
||||
use app\model\ApprovalFlow;
|
||||
use app\model\ApprovalRecord;
|
||||
use app\model\User;
|
||||
use app\util\Db;
|
||||
use Illuminate\Database\Connection;
|
||||
|
||||
class BusinessTripService
|
||||
@@ -165,15 +165,43 @@ class BusinessTripService
|
||||
|
||||
switch ($type) {
|
||||
case 'my':
|
||||
// 我发起的:当前用户创建的申请
|
||||
$query->where('user_id', $userId);
|
||||
break;
|
||||
case 'pending':
|
||||
// 获取待审批的申请
|
||||
// 待我审批:需要查询当前用户是审批人且还未审批的申请
|
||||
$pendingOrderIds = ApprovalOrder::where('status', ApprovalOrder::STATUS_PENDING)
|
||||
->where('business_type', 'business_trip')
|
||||
->pluck('business_id')
|
||||
->pluck('id')
|
||||
->toArray();
|
||||
$query->whereIn('id', $pendingOrderIds);
|
||||
|
||||
if (empty($pendingOrderIds)) {
|
||||
$query->whereRaw('1 = 0'); // 返回空结果
|
||||
} else {
|
||||
// 获取这些审批单中当前用户待审批的,排除已审批的
|
||||
$approvedOrderIds = ApprovalRecord::whereIn('order_id', $pendingOrderIds)
|
||||
->where('approver_id', $userId)
|
||||
->whereIn('action', ['approve', 'reject'])
|
||||
->pluck('order_id')
|
||||
->toArray();
|
||||
|
||||
$pendingOrderIds = array_diff($pendingOrderIds, $approvedOrderIds);
|
||||
|
||||
$query->whereIn('approval_order_id', $pendingOrderIds);
|
||||
}
|
||||
break;
|
||||
case 'approved':
|
||||
// 我已审批:当前用户已经审批过的申请
|
||||
$approvedOrderIds = ApprovalRecord::where('approver_id', $userId)
|
||||
->whereIn('action', ['approve', 'reject'])
|
||||
->pluck('order_id')
|
||||
->toArray();
|
||||
|
||||
if (empty($approvedOrderIds)) {
|
||||
$query->whereRaw('1 = 0'); // 返回空结果
|
||||
} else {
|
||||
$query->whereIn('approval_order_id', $approvedOrderIds);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -194,10 +222,32 @@ class BusinessTripService
|
||||
/**
|
||||
* 获取出差申请详情
|
||||
*/
|
||||
public function getDetail(int $id): BusinessTrip
|
||||
public function getDetail(int $id): array
|
||||
{
|
||||
return BusinessTrip::with(['user', 'itineraries'])
|
||||
$businessTrip = BusinessTrip::with(['user', 'itineraries'])
|
||||
->findOrFail($id);
|
||||
|
||||
$result = $businessTrip->toArray();
|
||||
|
||||
// 获取审批单信息
|
||||
if ($businessTrip->approval_order_id) {
|
||||
$approvalOrder = ApprovalOrder::with(['records.approver'])
|
||||
->find($businessTrip->approval_order_id);
|
||||
|
||||
if ($approvalOrder) {
|
||||
$result['approval_order'] = $approvalOrder->toArray();
|
||||
|
||||
// 获取审批人和抄送人信息
|
||||
$formData = $approvalOrder->form_data ?? [];
|
||||
$result['approvers'] = $formData['approvers'] ?? [];
|
||||
$result['cc_users'] = $formData['cc_users'] ?? [];
|
||||
|
||||
// 获取审批记录
|
||||
$result['approval_records'] = $approvalOrder->records->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -300,4 +350,113 @@ class BusinessTripService
|
||||
|
||||
return $businessTrip->fresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除草稿
|
||||
*/
|
||||
public function deleteDraft(int $userId, int $id): bool
|
||||
{
|
||||
$businessTrip = BusinessTrip::findOrFail($id);
|
||||
|
||||
if ($businessTrip->user_id !== $userId) {
|
||||
throw new \Exception('无权操作此申请');
|
||||
}
|
||||
|
||||
if ($businessTrip->status !== BusinessTrip::STATUS_DRAFT) {
|
||||
throw new \Exception('只能删除草稿');
|
||||
}
|
||||
|
||||
return $this->db->transaction(function () use ($businessTrip) {
|
||||
// 删除行程明细
|
||||
BusinessTripItinerary::where('business_trip_id', $businessTrip->id)->delete();
|
||||
|
||||
// 删除申请
|
||||
return $businessTrip->delete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批操作
|
||||
*/
|
||||
public function approve(int $userId, int $businessTripId, string $action, string $comment = ''): BusinessTrip
|
||||
{
|
||||
return $this->db->transaction(function () use ($userId, $businessTripId, $action, $comment) {
|
||||
$businessTrip = BusinessTrip::with(['itineraries', 'approvalOrder'])->findOrFail($businessTripId);
|
||||
|
||||
if (!$businessTrip->approval_order_id) {
|
||||
throw new \Exception('未找到审批单');
|
||||
}
|
||||
|
||||
$approvalOrder = $businessTrip->approvalOrder;
|
||||
|
||||
if ($approvalOrder->status !== ApprovalOrder::STATUS_PENDING) {
|
||||
throw new \Exception('当前状态无法审批');
|
||||
}
|
||||
|
||||
// 检查用户是否是当前审批人
|
||||
$currentNodes = $approvalOrder->current_nodes ?? [];
|
||||
$isApprover = false;
|
||||
|
||||
foreach ($currentNodes as $nodeId) {
|
||||
$node = ApprovalNode::where('order_id', $approvalOrder->id)
|
||||
->where('id', $nodeId)
|
||||
->first();
|
||||
|
||||
if (!$node) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查是否是单人审批节点
|
||||
if ($node->node_type === 'single_approve') {
|
||||
$approvers = $node->approvers ?? [];
|
||||
if (in_array($userId, $approvers)) {
|
||||
$isApprover = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isApprover) {
|
||||
throw new \Exception('您不是当前审批人');
|
||||
}
|
||||
|
||||
// 检查是否已经审批过
|
||||
$existingRecord = ApprovalRecord::where('order_id', $approvalOrder->id)
|
||||
->where('approver_id', $userId)
|
||||
->whereIn('action', ['approve', 'reject'])
|
||||
->first();
|
||||
|
||||
if ($existingRecord) {
|
||||
throw new \Exception('您已经审批过了');
|
||||
}
|
||||
|
||||
// 记录审批操作
|
||||
ApprovalRecord::create([
|
||||
'order_id' => $approvalOrder->id,
|
||||
'node_id' => $currentNodes[0] ?? 1,
|
||||
'node_type' => 'single_approve',
|
||||
'approver_id' => $userId,
|
||||
'action' => $action,
|
||||
'comment' => $comment,
|
||||
]);
|
||||
|
||||
// 更新审批单状态
|
||||
if ($action === 'reject') {
|
||||
// 拒绝:直接结束流程
|
||||
$approvalOrder->status = ApprovalOrder::STATUS_REJECTED;
|
||||
$approvalOrder->current_nodes = [];
|
||||
$businessTrip->status = BusinessTrip::STATUS_REJECTED;
|
||||
} else {
|
||||
// 同意:检查是否还有后续节点(简化版:直接通过)
|
||||
$approvalOrder->status = ApprovalOrder::STATUS_APPROVED;
|
||||
$approvalOrder->current_nodes = [];
|
||||
$businessTrip->status = BusinessTrip::STATUS_APPROVED;
|
||||
}
|
||||
|
||||
$approvalOrder->save();
|
||||
$businessTrip->save();
|
||||
|
||||
return $businessTrip->fresh(['itineraries', 'approvalOrder']);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
namespace app\service;
|
||||
|
||||
use App\Model\Company;
|
||||
use App\Model\CompanyFeishuApp;
|
||||
use App\Model\User;
|
||||
use App\Util\Db;
|
||||
use app\model\Company;
|
||||
use app\model\CompanyFeishuApp;
|
||||
use app\model\User;
|
||||
use app\util\Db;
|
||||
|
||||
class FeishuService
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Util;
|
||||
namespace app\util;
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Util;
|
||||
namespace app\util;
|
||||
|
||||
use Firebase\JWT\JWT;
|
||||
use Firebase\JWT\Key;
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace App\Util;
|
||||
namespace app\util;
|
||||
|
||||
/**
|
||||
* 多语言翻译类
|
||||
Reference in New Issue
Block a user