refactor: 重构后端目录结构和命名规范

- 将后端目录名从大写改为小写 (Controller->controller 等)
- 将命名空间从 App\ 改为 app\ (符合 webman 官方规范)
- 移除不必要的 bootstrap 目录
- 完善出差审批业务功能:
  - 添加删除草稿功能
  - 添加审批操作功能(同意/拒绝)
  - 完善审批详情页,显示审批人和审批记录
- 前端优化:
  - 调整列表页行高
  - 隐藏底部导航栏
  - 仪表盘只显示3个快捷入口

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-02-25 18:09:04 +08:00
parent 09d4cb8054
commit 582f454a5a
47 changed files with 1297 additions and 360 deletions
-15
View File
@@ -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();
}
}
-42
View File
@@ -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);
}
}
@@ -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;
@@ -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,6 +1,6 @@
<?php
namespace App\Controller;
namespace app\controller;
use Webman\Http\Request;
use Webman\Http\Response;
@@ -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());
}
}
/**
* 取消出差申请
*/
@@ -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;
+23 -12
View File
@@ -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,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
@@ -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;
/**
* 多语言翻译类
+1 -1
View File
@@ -19,7 +19,7 @@
},
"autoload": {
"psr-4": {
"App\\": "app/"
"app\\": "app/"
}
},
"scripts": {
-5
View File
@@ -1,5 +0,0 @@
<?php
return [
App\Bootstrap\Database::class,
];
+33 -32
View File
@@ -2,41 +2,42 @@
use Webman\Route;
// API 路由
// 认证路由(不需要认证)
Route::group('/api/v1', function () {
// 认证路由(不需要认证)
Route::post('/auth/login', [App\Controller\AuthController::class, 'login']);
Route::post('/auth/feishu-login', [App\Controller\AuthController::class, 'feishuLogin']);
// 用户路由(需要认证)
Route::group(function () {
Route::get('/user/info', [App\Controller\UserController::class, 'info']);
Route::get('/user/menu', [App\Controller\UserController::class, 'menu']);
Route::get('/user/companies', [App\Controller\UserController::class, 'companies']);
Route::get('/user/list', [App\Controller\UserController::class, 'list']);
Route::post('/user/switch-company', [App\Controller\UserController::class, 'switchCompany']);
});
// 审批路由(需要认证)
Route::group(function () {
Route::get('/approval/list', [App\Controller\ApprovalController::class, 'list']);
Route::get('/approval/detail/{id}', [App\Controller\ApprovalController::class, 'detail']);
Route::post('/approval/approve/{id}', [App\Controller\ApprovalController::class, 'approve']);
Route::post('/approval/reject/{id}', [App\Controller\ApprovalController::class, 'reject']);
Route::post('/approval/cancel/{id}', [App\Controller\ApprovalController::class, 'cancel']);
});
// 出差审批路由(需要认证)
Route::group(function () {
Route::get('/business-trip/list', [App\Controller\BusinessTripController::class, 'list']);
Route::get('/business-trip/detail/{id}', [App\Controller\BusinessTripController::class, 'detail']);
Route::post('/business-trip/create', [App\Controller\BusinessTripController::class, 'create']);
Route::post('/business-trip/update/{id}', [App\Controller\BusinessTripController::class, 'update']);
Route::post('/business-trip/submit/{id}', [App\Controller\BusinessTripController::class, 'submit']);
Route::post('/business-trip/cancel/{id}', [App\Controller\BusinessTripController::class, 'cancel']);
});
Route::post('/auth/login', [app\controller\AuthController::class, 'login']);
Route::post('/auth/feishu-login', [app\controller\AuthController::class, 'feishuLogin']);
});
// 用户路由(需要认证)
Route::group('/api/v1/user', function () {
Route::get('/info', [app\controller\UserController::class, 'info']);
Route::get('/menu', [app\controller\UserController::class, 'menu']);
Route::get('/companies', [app\controller\UserController::class, 'companies']);
Route::get('/list', [app\controller\UserController::class, 'list']);
Route::post('/switch-company', [app\controller\UserController::class, 'switchCompany']);
})->middleware([\app\middleware\AuthMiddleware::class]);
// 审批路由(需要认证)
Route::group('/api/v1/approval', function () {
Route::get('/list', [app\controller\ApprovalController::class, 'list']);
Route::get('/detail/{id}', [app\controller\ApprovalController::class, 'detail']);
Route::post('/approve/{id}', [app\controller\ApprovalController::class, 'approve']);
Route::post('/reject/{id}', [app\controller\ApprovalController::class, 'reject']);
Route::post('/cancel/{id}', [app\controller\ApprovalController::class, 'cancel']);
})->middleware([\app\middleware\AuthMiddleware::class]);
// 出差审批路由(需要认证)
Route::group('/api/v1/business-trip', function () {
Route::get('/list', [app\controller\BusinessTripController::class, 'list']);
Route::get('/detail/{id}', [app\controller\BusinessTripController::class, 'detail']);
Route::post('/create', [app\controller\BusinessTripController::class, 'create']);
Route::post('/update/{id}', [app\controller\BusinessTripController::class, 'update']);
Route::post('/submit/{id}', [app\controller\BusinessTripController::class, 'submit']);
Route::post('/cancel/{id}', [app\controller\BusinessTripController::class, 'cancel']);
Route::delete('/{id}', [app\controller\BusinessTripController::class, 'deleteDraft']);
Route::post('/approve/{id}', [app\controller\BusinessTripController::class, 'approve']);
})->middleware([\app\middleware\AuthMiddleware::class]);
// 默认路由
Route::fallback(function () {
return json(['code' => 404, 'message' => 'Not Found']);
+1 -1
View File
@@ -11,7 +11,7 @@
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../app/Util/Db.php';
use App\Util\Db;
use app\util\Db;
class DataImporter
{