fix: 修复页面间状态丢失问题并优化选择页面
- 修复选择审批人后再选择抄送人导致状态丢失的问题 - 使用独立的 sessionStorage key (selectedApprovers, selectedCc) - 组件每次挂载时从 sessionStorage 恢复状态 - 只在提交/保存成功后清除 sessionStorage - 优化人员选择页面布局和样式 - 更新 CLAUDE.md 文档记录问题和解决方案 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Service\ApprovalService;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
class ApprovalController extends BaseController
|
||||
{
|
||||
private ApprovalService $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new ApprovalService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审批列表
|
||||
*/
|
||||
public function list(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$type = $request->get('type', 'my');
|
||||
$page = (int) $request->get('page', 1);
|
||||
$pageSize = (int) $request->get('page_size', 20);
|
||||
|
||||
$result = $this->service->getList($userId, $type, $page, $pageSize);
|
||||
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审批详情
|
||||
*/
|
||||
public function detail(Request $request): Response
|
||||
{
|
||||
$id = (int) $request->route('id');
|
||||
|
||||
$approval = \App\Model\ApprovalOrder::with(['flow', 'applicant', 'records'])
|
||||
->findOrFail($id);
|
||||
|
||||
return $this->success($approval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批通过
|
||||
*/
|
||||
public function approve(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$id = (int) $request->route('id');
|
||||
$data = $request->post();
|
||||
$data = $data ?? [];
|
||||
$comment = $data['comment'] ?? null;
|
||||
|
||||
try {
|
||||
$approval = $this->service->approve($id, $userId, $comment);
|
||||
return $this->success($approval, '已通过');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批拒绝
|
||||
*/
|
||||
public function reject(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$id = (int) $request->route('id');
|
||||
$data = $request->post();
|
||||
$data = $data ?? [];
|
||||
$comment = $data['comment'] ?? null;
|
||||
|
||||
try {
|
||||
$approval = $this->service->reject($id, $userId, $comment);
|
||||
return $this->success($approval, '已拒绝');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销申请
|
||||
*/
|
||||
public function cancel(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$id = (int) $request->route('id');
|
||||
|
||||
try {
|
||||
$approval = $this->service->cancel($id, $userId);
|
||||
return $this->success($approval, '已撤销');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Service\FeishuService;
|
||||
use App\Util\JwtUtil;
|
||||
use App\Model\User;
|
||||
use App\Model\Company;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
class AuthController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
public function login(Request $request): Response
|
||||
{
|
||||
$username = $request->post('username');
|
||||
$password = $request->post('password');
|
||||
|
||||
// TODO: 实现真实的登录逻辑
|
||||
if (!$username || !$password) {
|
||||
return $this->error('用户名或密码不能为空');
|
||||
}
|
||||
|
||||
// 模拟用户数据(用于测试)
|
||||
$user = User::first();
|
||||
|
||||
if (!$user) {
|
||||
return $this->error('用户不存在');
|
||||
}
|
||||
|
||||
$token = JwtUtil::encode([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company_id,
|
||||
'username' => $user->name,
|
||||
]);
|
||||
|
||||
return $this->success([
|
||||
'token' => $token,
|
||||
'user' => $user,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 飞书免登
|
||||
*/
|
||||
public function feishuLogin(Request $request): Response
|
||||
{
|
||||
$authCode = $request->post('auth_code');
|
||||
$tenantKey = $request->post('tenant_key');
|
||||
|
||||
if (!$authCode) {
|
||||
return $this->error('缺少授权码');
|
||||
}
|
||||
|
||||
try {
|
||||
$feishuService = new FeishuService();
|
||||
|
||||
// 根据 tenant_key 获取公司
|
||||
$company = $feishuService->getCompanyByTenantKey($tenantKey);
|
||||
|
||||
if (!$company) {
|
||||
return $this->error('公司不存在');
|
||||
}
|
||||
|
||||
// 设置公司并获取用户信息
|
||||
$feishuService->setCompany($company->id);
|
||||
$feishuData = $feishuService->getUserInfo($authCode);
|
||||
|
||||
// 创建或更新用户
|
||||
$user = $feishuService->createOrUpdateUser($feishuData, $company->id);
|
||||
|
||||
// 生成 JWT Token
|
||||
$token = JwtUtil::encode([
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $user->company_id,
|
||||
'feishu_user_id' => $user->feishu_user_id,
|
||||
'union_id' => $user->union_id,
|
||||
]);
|
||||
|
||||
return $this->success([
|
||||
'token' => $token,
|
||||
'user' => $user->load('company'),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
public function me(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
|
||||
if (!$userId) {
|
||||
return $this->error('未登录', 401);
|
||||
}
|
||||
|
||||
$user = User::with('company')->find($userId);
|
||||
|
||||
if (!$user) {
|
||||
return $this->error('用户不存在');
|
||||
}
|
||||
|
||||
return $this->success($user);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
abstract class BaseController
|
||||
{
|
||||
/**
|
||||
* 成功响应
|
||||
*/
|
||||
protected function success($data = null, string $message = 'success'): Response
|
||||
{
|
||||
return json([
|
||||
'code' => 0,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误响应
|
||||
*/
|
||||
protected function error(string $message = 'error', int $code = 1, $data = null): Response
|
||||
{
|
||||
return json([
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户ID
|
||||
*/
|
||||
protected function getUserId(Request $request): ?int
|
||||
{
|
||||
// 先检查 request 中是否已注入
|
||||
if (isset($request->userId)) {
|
||||
return $request->userId;
|
||||
}
|
||||
|
||||
// 从 Authorization header 获取 token
|
||||
$token = $request->header('Authorization');
|
||||
if ($token) {
|
||||
$token = str_replace('Bearer ', '', $token);
|
||||
}
|
||||
|
||||
if (!$token) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 解析 token
|
||||
$payload = \App\Util\JwtUtil::decode($token);
|
||||
if (!$payload) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (int) ($payload['user_id'] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证认证并返回用户ID,失败则抛出异常
|
||||
*/
|
||||
protected function requireAuth(Request $request): int
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
if (!$userId) {
|
||||
throw new \Exception('未登录或登录已过期', 401);
|
||||
}
|
||||
return $userId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Service\BusinessTripService;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
class BusinessTripController extends BaseController
|
||||
{
|
||||
private BusinessTripService $service;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->service = new BusinessTripService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取路由参数
|
||||
*/
|
||||
private function getRouteParam(Request $request, string $name, $default = null)
|
||||
{
|
||||
$route = $request->route;
|
||||
if ($route && method_exists($route, 'param')) {
|
||||
return $route->param($name) ?? $default;
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取出差申请列表
|
||||
*/
|
||||
public function list(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$type = $request->get('type', 'my');
|
||||
$page = (int) $request->get('page', 1);
|
||||
$pageSize = (int) $request->get('page_size', 20);
|
||||
|
||||
$result = $this->service->getList($userId, $type, $page, $pageSize);
|
||||
|
||||
return $this->success($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取出差申请详情
|
||||
*/
|
||||
public function detail(Request $request): Response
|
||||
{
|
||||
$id = (int) $this->getRouteParam($request, 'id');
|
||||
if (!$id) {
|
||||
return $this->error('参数错误');
|
||||
}
|
||||
|
||||
$detail = $this->service->getDetail($id);
|
||||
|
||||
return $this->success($detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建出差申请(草稿)
|
||||
*/
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$data = $request->post();
|
||||
$data = $data ?? [];
|
||||
|
||||
// 验证必填字段
|
||||
if (empty($data['reason'])) {
|
||||
return $this->error('请输入出差原因');
|
||||
}
|
||||
|
||||
if (empty($data['itineraries']) || !is_array($data['itineraries'])) {
|
||||
return $this->error('请至少添加一条行程');
|
||||
}
|
||||
|
||||
// 验证行程数据
|
||||
foreach ($data['itineraries'] as $index => $itinerary) {
|
||||
if (empty($itinerary['start_time'])) {
|
||||
return $this->error("行程" . ($index + 1) . ":请选择开始时间");
|
||||
}
|
||||
if (empty($itinerary['destination'])) {
|
||||
return $this->error("行程" . ($index + 1) . ":请输入目的地");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$businessTrip = $this->service->createDraft($userId, $data);
|
||||
return $this->success($businessTrip, '创建成功');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新出差申请
|
||||
*/
|
||||
public function update(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$id = (int) $this->getRouteParam($request, 'id');
|
||||
if (!$id) {
|
||||
return $this->error('参数错误');
|
||||
}
|
||||
|
||||
$data = $request->post();
|
||||
$data = $data ?? [];
|
||||
|
||||
try {
|
||||
$businessTrip = $this->service->update($userId, $id, $data);
|
||||
return $this->success($businessTrip, '更新成功');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交出差申请
|
||||
*/
|
||||
public function submit(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$id = (int) $this->getRouteParam($request, 'id');
|
||||
if (!$id) {
|
||||
return $this->error('参数错误');
|
||||
}
|
||||
|
||||
$data = $request->post();
|
||||
$data = $data ?? [];
|
||||
|
||||
$approvers = $data['approvers'] ?? [];
|
||||
$ccUsers = $data['cc_users'] ?? [];
|
||||
|
||||
if (empty($approvers)) {
|
||||
return $this->error('请选择审批人');
|
||||
}
|
||||
|
||||
try {
|
||||
$businessTrip = $this->service->submit($userId, $id, $approvers, $ccUsers);
|
||||
return $this->success($businessTrip, '提交成功');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消出差申请
|
||||
*/
|
||||
public function cancel(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$id = (int) $this->getRouteParam($request, 'id');
|
||||
if (!$id) {
|
||||
return $this->error('参数错误');
|
||||
}
|
||||
|
||||
try {
|
||||
$businessTrip = $this->service->cancel($userId, $id);
|
||||
return $this->success($businessTrip, '取消成功');
|
||||
} catch (\Exception $e) {
|
||||
return $this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
-8
@@ -1,7 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace app\controller;
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Model\User;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
@@ -13,14 +14,13 @@ class UserController extends BaseController
|
||||
public function info(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$user = User::find($userId);
|
||||
|
||||
// TODO: 从数据库获取用户信息
|
||||
return $this->success([
|
||||
'id' => $userId,
|
||||
'name' => '测试用户',
|
||||
'email' => 'test@example.com',
|
||||
'company_id' => 1,
|
||||
]);
|
||||
if (!$user) {
|
||||
return $this->error('用户不存在');
|
||||
}
|
||||
|
||||
return $this->success($user);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,4 +59,34 @@ class UserController extends BaseController
|
||||
|
||||
return $this->success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户列表(用于选择审批人等)
|
||||
*/
|
||||
public function list(Request $request): Response
|
||||
{
|
||||
$userId = $this->getUserId($request);
|
||||
$keyword = $request->get('keyword', '');
|
||||
$page = (int) $request->get('page', 1);
|
||||
$pageSize = (int) $request->get('page_size', 50);
|
||||
|
||||
$query = User::active();
|
||||
|
||||
if ($keyword) {
|
||||
$query->search($keyword);
|
||||
}
|
||||
|
||||
$total = $query->count();
|
||||
$list = $query->orderBy('id')
|
||||
->offset(($page - 1) * $pageSize)
|
||||
->limit($pageSize)
|
||||
->get();
|
||||
|
||||
return $this->success([
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'page_size' => $pageSize,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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\model;
|
||||
namespace App\Model;
|
||||
|
||||
use support\Model;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ApprovalFlow extends Model
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace app\model;
|
||||
namespace App\Model;
|
||||
|
||||
use support\Model;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ApprovalNode extends Model
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace app\model;
|
||||
namespace App\Model;
|
||||
|
||||
use support\Model;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ApprovalOrder extends Model
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace app\model;
|
||||
namespace App\Model;
|
||||
|
||||
use support\Model;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ApprovalRecord extends Model
|
||||
{
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class BusinessTrip extends Model
|
||||
{
|
||||
protected $table = 'business_trip';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'reason',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'destination',
|
||||
'estimated_cost',
|
||||
'status',
|
||||
'approval_order_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'user_id' => 'integer',
|
||||
'estimated_cost' => 'decimal:2',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* 状态常量
|
||||
*/
|
||||
const STATUS_DRAFT = 'draft'; // 草稿
|
||||
const STATUS_PENDING = 'pending'; // 审批中
|
||||
const STATUS_APPROVED = 'approved'; // 已通过
|
||||
const STATUS_REJECTED = 'rejected'; // 已拒绝
|
||||
const STATUS_CANCELLED = 'cancelled'; // 已撤销
|
||||
|
||||
/**
|
||||
* 关联用户
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联行程明细
|
||||
*/
|
||||
public function itineraries()
|
||||
{
|
||||
return $this->hasMany(BusinessTripItinerary::class, 'business_trip_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联审批单
|
||||
*/
|
||||
public function approvalOrder()
|
||||
{
|
||||
return $this->belongsTo(ApprovalOrder::class, 'approval_order_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算总天数
|
||||
*/
|
||||
public function getDurationDays(): int
|
||||
{
|
||||
if (!$this->start_date || !$this->end_date) {
|
||||
return 0;
|
||||
}
|
||||
$start = \DateTime::createFromFormat('Y-m-d', $this->start_date);
|
||||
$end = \DateTime::createFromFormat('Y-m-d', $this->end_date);
|
||||
if (!$start || !$end) {
|
||||
return 0;
|
||||
}
|
||||
return $end->diff($start)->days + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否可以编辑
|
||||
*/
|
||||
public function canEdit(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_DRAFT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否可以提交
|
||||
*/
|
||||
public function canSubmit(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_DRAFT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否可以取消
|
||||
*/
|
||||
public function canCancel(): bool
|
||||
{
|
||||
return $this->status === self::STATUS_PENDING;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class BusinessTripItinerary extends Model
|
||||
{
|
||||
protected $table = 'business_trip_itinerary';
|
||||
|
||||
protected $fillable = [
|
||||
'business_trip_id',
|
||||
'start_time',
|
||||
'end_time',
|
||||
'departure',
|
||||
'destination',
|
||||
'transport',
|
||||
'trip_type',
|
||||
'remark',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'business_trip_id' => 'integer',
|
||||
'start_time' => 'datetime',
|
||||
'end_time' => 'datetime',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* 行程类型常量
|
||||
*/
|
||||
const TRIP_TYPE_ONEWAY = 'oneway'; // 单程
|
||||
const TRIP_TYPE_ROUNDTRIP = 'roundtrip'; // 往返
|
||||
|
||||
/**
|
||||
* 关联出差申请
|
||||
*/
|
||||
public function businessTrip()
|
||||
{
|
||||
return $this->belongsTo(BusinessTrip::class, 'business_trip_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算天数
|
||||
*/
|
||||
public function getDurationDays(): int
|
||||
{
|
||||
if (!$this->start_time || !$this->end_time) {
|
||||
return 0;
|
||||
}
|
||||
$start = \DateTime::createFromFormat('Y-m-d H:i:s', $this->start_time);
|
||||
$end = \DateTime::createFromFormat('Y-m-d H:i:s', $this->end_time);
|
||||
if (!$start || !$end) {
|
||||
return 0;
|
||||
}
|
||||
return $end->diff($start)->days + 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Company extends Model
|
||||
{
|
||||
protected $table = 'companies';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'abbr',
|
||||
'tenant_key',
|
||||
'sort',
|
||||
'status',
|
||||
'feishu_enabled',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'sort' => 'integer',
|
||||
'status' => 'integer',
|
||||
'feishu_enabled' => 'boolean',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取启用的公司
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('status', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 tenant_key 查找
|
||||
*/
|
||||
public function scopeByTenantKey($query, ?string $tenantKey)
|
||||
{
|
||||
if ($tenantKey === null) {
|
||||
return $query;
|
||||
}
|
||||
return $query->where('tenant_key', $tenantKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联飞书应用配置
|
||||
*/
|
||||
public function feishuApps()
|
||||
{
|
||||
return $this->hasMany(CompanyFeishuApp::class, 'company_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联用户
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany(User::class, 'company_id');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CompanyFeishuApp extends Model
|
||||
{
|
||||
protected $table = 'company_feishu_apps';
|
||||
|
||||
protected $fillable = [
|
||||
'company_id',
|
||||
'system_id',
|
||||
'name',
|
||||
'tenant_key',
|
||||
'app_id',
|
||||
'app_secret',
|
||||
'base_uri',
|
||||
'callback_url',
|
||||
'mobile_callback_url',
|
||||
'status',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'company_id' => 'integer',
|
||||
'system_id' => 'integer',
|
||||
'status' => 'integer',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* 获取启用的应用
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('status', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联公司
|
||||
*/
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class, 'company_id');
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace app\model;
|
||||
namespace App\Model;
|
||||
|
||||
use support\Model;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class User extends 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
|
||||
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
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 Illuminate\Database\Connection;
|
||||
|
||||
class BusinessTripService
|
||||
{
|
||||
private Connection $db;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
Db::init();
|
||||
$this->db = Db::connection();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建出差申请(草稿)
|
||||
*/
|
||||
public function createDraft(int $userId, array $data): BusinessTrip
|
||||
{
|
||||
return $this->db->transaction(function () use ($userId, $data) {
|
||||
try {
|
||||
// 计算最早和最晚时间
|
||||
$itineraries = $data['itineraries'] ?? [];
|
||||
if (empty($itineraries)) {
|
||||
throw new \Exception('请至少添加一条行程');
|
||||
}
|
||||
|
||||
$startDate = null;
|
||||
$endDate = null;
|
||||
$destinations = [];
|
||||
|
||||
foreach ($itineraries as $itinerary) {
|
||||
if ($itinerary['start_time']) {
|
||||
if (!$startDate || $itinerary['start_time'] < $startDate) {
|
||||
$startDate = $itinerary['start_time'];
|
||||
}
|
||||
}
|
||||
if ($itinerary['end_time']) {
|
||||
if (!$endDate || $itinerary['end_time'] > $endDate) {
|
||||
$endDate = $itinerary['end_time'];
|
||||
}
|
||||
}
|
||||
if (!empty($itinerary['destination'])) {
|
||||
$destinations[] = $itinerary['destination'];
|
||||
}
|
||||
}
|
||||
|
||||
// 创建出差申请
|
||||
$businessTrip = BusinessTrip::create([
|
||||
'user_id' => $userId,
|
||||
'reason' => $data['reason'] ?? '',
|
||||
'start_date' => $startDate ? date('Y-m-d', strtotime($startDate)) : null,
|
||||
'end_date' => $endDate ? date('Y-m-d', strtotime($endDate)) : null,
|
||||
'destination' => implode(', ', array_unique($destinations)),
|
||||
'estimated_cost' => $data['estimated_cost'] ?? 0,
|
||||
'status' => BusinessTrip::STATUS_DRAFT,
|
||||
]);
|
||||
|
||||
// 创建行程明细
|
||||
foreach ($itineraries as $itinerary) {
|
||||
BusinessTripItinerary::create([
|
||||
'business_trip_id' => $businessTrip->id,
|
||||
'start_time' => $itinerary['start_time'],
|
||||
'end_time' => $itinerary['end_time'] ?? null,
|
||||
'departure' => $itinerary['departure'] ?? '',
|
||||
'destination' => $itinerary['destination'] ?? '',
|
||||
'transport' => $itinerary['transport'] ?? '',
|
||||
'trip_type' => $itinerary['trip_type'] ?? 'oneway',
|
||||
'remark' => $itinerary['remark'] ?? '',
|
||||
]);
|
||||
}
|
||||
|
||||
return $businessTrip->load('itineraries');
|
||||
} catch (\Exception $e) {
|
||||
error_log('BusinessTripService::createDraft error: ' . $e->getMessage() . ' in ' . $e->getFile() . ':' . $e->getLine());
|
||||
throw $e;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交出差申请
|
||||
*/
|
||||
public function submit(int $userId, int $businessTripId, array $approvers, array $ccUsers = []): BusinessTrip
|
||||
{
|
||||
return $this->db->transaction(function () use ($userId, $businessTripId, $approvers, $ccUsers) {
|
||||
$businessTrip = BusinessTrip::with('itineraries')->findOrFail($businessTripId);
|
||||
|
||||
if ($businessTrip->user_id !== $userId) {
|
||||
throw new \Exception('无权操作此申请');
|
||||
}
|
||||
|
||||
if (!$businessTrip->canSubmit()) {
|
||||
throw new \Exception('当前状态无法提交');
|
||||
}
|
||||
|
||||
// 获取审批流程(简化版:直接使用第一个审批人作为单人审批)
|
||||
$flow = ApprovalFlow::where('business_type', 'business_trip')
|
||||
->where('status', 1)
|
||||
->first();
|
||||
|
||||
if (!$flow) {
|
||||
// 创建默认流程
|
||||
$flow = ApprovalFlow::create([
|
||||
'business_type' => 'business_trip',
|
||||
'name' => '出差审批',
|
||||
'description' => '出差申请审批流程',
|
||||
'status' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
// 创建审批单
|
||||
$approvalOrder = ApprovalOrder::create([
|
||||
'business_type' => 'business_trip',
|
||||
'business_id' => $businessTrip->id,
|
||||
'title' => '出差申请 - ' . $businessTrip->user->name,
|
||||
'applicant_id' => $userId,
|
||||
'status' => ApprovalOrder::STATUS_PENDING,
|
||||
'current_nodes' => [1], // 简化:使用节点1
|
||||
'flow_id' => $flow->id,
|
||||
'form_data' => [
|
||||
'reason' => $businessTrip->reason,
|
||||
'start_date' => $businessTrip->start_date,
|
||||
'end_date' => $businessTrip->end_date,
|
||||
'destination' => $businessTrip->destination,
|
||||
'approvers' => $approvers,
|
||||
'cc_users' => $ccUsers,
|
||||
],
|
||||
]);
|
||||
|
||||
// 更新出差申请状态
|
||||
$businessTrip->status = BusinessTrip::STATUS_PENDING;
|
||||
$businessTrip->approval_order_id = $approvalOrder->id;
|
||||
$businessTrip->save();
|
||||
|
||||
// 记录提交操作
|
||||
ApprovalRecord::create([
|
||||
'order_id' => $approvalOrder->id,
|
||||
'node_id' => 1,
|
||||
'node_type' => 'single_approve',
|
||||
'approver_id' => $userId,
|
||||
'action' => 'submit',
|
||||
'comment' => null,
|
||||
]);
|
||||
|
||||
return $businessTrip->fresh(['itineraries', 'approvalOrder']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取出差申请列表
|
||||
*/
|
||||
public function getList(int $userId, string $type = 'my', int $page = 1, int $pageSize = 20): array
|
||||
{
|
||||
$query = BusinessTrip::with(['user', 'itineraries']);
|
||||
|
||||
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')
|
||||
->toArray();
|
||||
$query->whereIn('id', $pendingOrderIds);
|
||||
break;
|
||||
}
|
||||
|
||||
$total = $query->count();
|
||||
$list = $query->orderBy('created_at', 'desc')
|
||||
->offset(($page - 1) * $pageSize)
|
||||
->limit($pageSize)
|
||||
->get();
|
||||
|
||||
return [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'page_size' => $pageSize,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取出差申请详情
|
||||
*/
|
||||
public function getDetail(int $id): BusinessTrip
|
||||
{
|
||||
return BusinessTrip::with(['user', 'itineraries'])
|
||||
->findOrFail($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新出差申请
|
||||
*/
|
||||
public function update(int $userId, int $id, array $data): BusinessTrip
|
||||
{
|
||||
$businessTrip = BusinessTrip::findOrFail($id);
|
||||
|
||||
if ($businessTrip->user_id !== $userId) {
|
||||
throw new \Exception('无权操作此申请');
|
||||
}
|
||||
|
||||
if (!$businessTrip->canEdit()) {
|
||||
throw new \Exception('当前状态无法编辑');
|
||||
}
|
||||
|
||||
return $this->db->transaction(function () use ($businessTrip, $data) {
|
||||
// 更新基本信息
|
||||
if (isset($data['reason'])) {
|
||||
$businessTrip->reason = $data['reason'];
|
||||
}
|
||||
|
||||
// 更新行程
|
||||
if (isset($data['itineraries'])) {
|
||||
// 删除旧行程
|
||||
BusinessTripItinerary::where('business_trip_id', $businessTrip->id)->delete();
|
||||
|
||||
// 创建新行程
|
||||
$startDate = null;
|
||||
$endDate = null;
|
||||
$destinations = [];
|
||||
|
||||
foreach ($data['itineraries'] as $itinerary) {
|
||||
BusinessTripItinerary::create([
|
||||
'business_trip_id' => $businessTrip->id,
|
||||
'start_time' => $itinerary['start_time'],
|
||||
'end_time' => $itinerary['end_time'] ?? null,
|
||||
'departure' => $itinerary['departure'] ?? '',
|
||||
'destination' => $itinerary['destination'] ?? '',
|
||||
'transport' => $itinerary['transport'] ?? '',
|
||||
'trip_type' => $itinerary['trip_type'] ?? 'oneway',
|
||||
'remark' => $itinerary['remark'] ?? '',
|
||||
]);
|
||||
|
||||
if ($itinerary['start_time']) {
|
||||
if (!$startDate || $itinerary['start_time'] < $startDate) {
|
||||
$startDate = $itinerary['start_time'];
|
||||
}
|
||||
}
|
||||
if ($itinerary['end_time']) {
|
||||
if (!$endDate || $itinerary['end_time'] > $endDate) {
|
||||
$endDate = $itinerary['end_time'];
|
||||
}
|
||||
}
|
||||
if (!empty($itinerary['destination'])) {
|
||||
$destinations[] = $itinerary['destination'];
|
||||
}
|
||||
}
|
||||
|
||||
// 更新日期和目的地
|
||||
$businessTrip->start_date = $startDate ? date('Y-m-d', strtotime($startDate)) : null;
|
||||
$businessTrip->end_date = $endDate ? date('Y-m-d', strtotime($endDate)) : null;
|
||||
$businessTrip->destination = implode(', ', array_unique($destinations));
|
||||
}
|
||||
|
||||
$businessTrip->save();
|
||||
|
||||
return $businessTrip->fresh('itineraries');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消出差申请
|
||||
*/
|
||||
public function cancel(int $userId, int $id): BusinessTrip
|
||||
{
|
||||
$businessTrip = BusinessTrip::findOrFail($id);
|
||||
|
||||
if ($businessTrip->user_id !== $userId) {
|
||||
throw new \Exception('无权操作此申请');
|
||||
}
|
||||
|
||||
if (!$businessTrip->canCancel()) {
|
||||
throw new \Exception('当前状态无法取消');
|
||||
}
|
||||
|
||||
$businessTrip->status = BusinessTrip::STATUS_CANCELLED;
|
||||
$businessTrip->save();
|
||||
|
||||
// 同时取消审批单
|
||||
if ($businessTrip->approval_order_id) {
|
||||
$approvalOrder = ApprovalOrder::find($businessTrip->approval_order_id);
|
||||
if ($approvalOrder) {
|
||||
$approvalOrder->status = ApprovalOrder::STATUS_CANCELLED;
|
||||
$approvalOrder->current_nodes = [];
|
||||
$approvalOrder->save();
|
||||
}
|
||||
}
|
||||
|
||||
return $businessTrip->fresh();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace app\service;
|
||||
namespace App\Service;
|
||||
|
||||
use app\model\Company;
|
||||
use app\model\User;
|
||||
use app\util\Db;
|
||||
use support\Cache;
|
||||
use App\Model\Company;
|
||||
use App\Model\CompanyFeishuApp;
|
||||
use App\Model\User;
|
||||
use App\Util\Db;
|
||||
|
||||
class FeishuService
|
||||
{
|
||||
@@ -13,6 +13,9 @@ class FeishuService
|
||||
private ?int $companyId = null;
|
||||
private ?int $appId = null;
|
||||
|
||||
// 简单的内存缓存(生产环境应使用 Redis)
|
||||
private static array $tokenCache = [];
|
||||
|
||||
/**
|
||||
* 设置当前操作的公司
|
||||
*/
|
||||
@@ -46,16 +49,13 @@ class FeishuService
|
||||
*/
|
||||
private function getFeishuApp(int $appId): array
|
||||
{
|
||||
$sql = "SELECT * FROM company_feishu_apps WHERE id = ? AND status = 1";
|
||||
$stmt = Db::connection()->getPdo()->prepare($sql);
|
||||
$stmt->execute([$appId]);
|
||||
$app = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
$app = CompanyFeishuApp::find($appId);
|
||||
|
||||
if (!$app) {
|
||||
if (!$app || $app->status !== 1) {
|
||||
throw new \Exception('飞书应用配置不存在');
|
||||
}
|
||||
|
||||
return $app;
|
||||
return $app->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,16 +63,17 @@ class FeishuService
|
||||
*/
|
||||
private function getDefaultFeishuApp(int $companyId): array
|
||||
{
|
||||
$sql = "SELECT * FROM company_feishu_apps WHERE company_id = ? AND system_id = 1 AND status = 1 ORDER BY sort ASC LIMIT 1";
|
||||
$stmt = Db::connection()->getPdo()->prepare($sql);
|
||||
$stmt->execute([$companyId]);
|
||||
$app = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
$app = CompanyFeishuApp::where('company_id', $companyId)
|
||||
->where('system_id', 1)
|
||||
->where('status', 1)
|
||||
->orderBy('sort')
|
||||
->first();
|
||||
|
||||
if (!$app) {
|
||||
throw new \Exception('未配置飞书应用');
|
||||
}
|
||||
|
||||
return $app;
|
||||
return $app->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,14 +102,12 @@ class FeishuService
|
||||
}
|
||||
|
||||
$app = $this->getAppConfig();
|
||||
|
||||
// 从缓存获取
|
||||
$cacheKey = "feishu:access_token:{$app['id']}";
|
||||
$token = Cache::get($cacheKey);
|
||||
|
||||
if ($token) {
|
||||
$this->accessToken = $token;
|
||||
return $token;
|
||||
// 从内存缓存获取
|
||||
if (isset(self::$tokenCache[$cacheKey]) && self::$tokenCache[$cacheKey]['expire'] > time()) {
|
||||
$this->accessToken = self::$tokenCache[$cacheKey]['token'];
|
||||
return $this->accessToken;
|
||||
}
|
||||
|
||||
// 请求新的 token
|
||||
@@ -125,7 +124,10 @@ class FeishuService
|
||||
$expire = $response['expire'] ?? 7200;
|
||||
|
||||
// 缓存 token(提前5分钟过期)
|
||||
Cache::set($cacheKey, $token, $expire - 300);
|
||||
self::$tokenCache[$cacheKey] = [
|
||||
'token' => $token,
|
||||
'expire' => time() + $expire - 300,
|
||||
];
|
||||
|
||||
$this->accessToken = $token;
|
||||
return $token;
|
||||
@@ -290,7 +292,7 @@ class FeishuService
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
// curl_close($ch); // 不再需要,PHP 8.5+ 自动关闭
|
||||
|
||||
if ($httpCode !== 200) {
|
||||
throw new \Exception("HTTP 请求失败: {$httpCode}");
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace app\util;
|
||||
namespace App\Util;
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
@@ -24,11 +24,11 @@ class Db
|
||||
// Add connection
|
||||
self::$capsule->addConnection([
|
||||
'driver' => 'mysql',
|
||||
'host' => getenv('DB_HOST') ?: '127.0.0.1',
|
||||
'host' => getenv('DB_HOST') ?: '1Panel-mysql-409s',
|
||||
'port' => getenv('DB_PORT') ?: '3306',
|
||||
'database' => getenv('DB_DATABASE') ?: 'xserver',
|
||||
'username' => getenv('DB_USERNAME') ?: 'root',
|
||||
'password' => getenv('DB_PASSWORD') ?: '',
|
||||
'password' => getenv('DB_PASSWORD') ?: 'mysql_xEsdPa',
|
||||
'charset' => 'utf8mb4',
|
||||
'collation' => 'utf8mb4_unicode_ci',
|
||||
'prefix' => '',
|
||||
@@ -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,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
use app\util\JwtUtil;
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
class AuthController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
public function login(Request $request): Response
|
||||
{
|
||||
$username = $request->post('username');
|
||||
$password = $request->post('password');
|
||||
|
||||
// TODO: 实现真实的登录逻辑
|
||||
if (!$username || !$password) {
|
||||
return $this->error('用户名或密码不能空');
|
||||
}
|
||||
|
||||
// 模拟用户数据
|
||||
$user = [
|
||||
'user_id' => 1,
|
||||
'username' => $username,
|
||||
'name' => '测试用户',
|
||||
'company_id' => 1,
|
||||
];
|
||||
|
||||
$token = JwtUtil::encode($user);
|
||||
|
||||
return $this->success([
|
||||
'token' => $token,
|
||||
'user' => $user,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 飞书免登
|
||||
*/
|
||||
public function feishuLogin(Request $request): Response
|
||||
{
|
||||
$authCode = $request->post('auth_code');
|
||||
$tenantKey = $request->post('tenant_key');
|
||||
|
||||
// TODO: 实现飞书免登逻辑
|
||||
// 1. 根据 tenant_key 获取公司配置
|
||||
// 2. 使用 auth_code 换取用户信息
|
||||
// 3. 创建或更新用户
|
||||
// 4. 生成 JWT Token
|
||||
|
||||
return $this->success([
|
||||
'token' => JwtUtil::encode(['user_id' => 1, 'company_id' => 1]),
|
||||
'user' => ['id' => 1, 'name' => '飞书用户', 'company_id' => 1],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\controller;
|
||||
|
||||
use Webman\Http\Request;
|
||||
use Webman\Http\Response;
|
||||
|
||||
abstract class BaseController
|
||||
{
|
||||
/**
|
||||
* 成功响应
|
||||
*/
|
||||
protected function success($data = null, string $message = 'success'): Response
|
||||
{
|
||||
return json([
|
||||
'code' => 0,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误响应
|
||||
*/
|
||||
protected function error(string $message = 'error', int $code = 1, $data = null): Response
|
||||
{
|
||||
return json([
|
||||
'code' => $code,
|
||||
'message' => $message,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户ID
|
||||
*/
|
||||
protected function getUserId(Request $request): ?int
|
||||
{
|
||||
return $request->userId ?? null;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\model;
|
||||
|
||||
use support\Model;
|
||||
|
||||
class Company extends Model
|
||||
{
|
||||
protected $table = 'companies';
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'abbr',
|
||||
'tenant_key',
|
||||
'sort',
|
||||
'status',
|
||||
'feishu_enabled',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'sort' => 'integer',
|
||||
'status' => 'integer',
|
||||
'feishu_enabled' => 'boolean',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* 关联用户
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany(User::class, 'company_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启用的公司
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
return $query->where('status', 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据租户Key查找
|
||||
*/
|
||||
public function scopeByTenantKey($query, string $tenantKey)
|
||||
{
|
||||
return $query->where('tenant_key', $tenantKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据缩写查找
|
||||
*/
|
||||
public function scopeByAbbr($query, string $abbr)
|
||||
{
|
||||
return $query->where('abbr', $abbr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索公司
|
||||
*/
|
||||
public function scopeSearch($query, ?string $keyword)
|
||||
{
|
||||
if (!$keyword) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
return $query->where(function ($q) use ($keyword) {
|
||||
$q->where('name', 'like', "%{$keyword}%")
|
||||
->orWhere('abbr', 'like', "%{$keyword}%");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认飞书应用配置
|
||||
*/
|
||||
public function getDefaultFeishuApp(): ?array
|
||||
{
|
||||
$sql = "SELECT * FROM company_feishu_apps WHERE company_id = ? AND system_id = 1 AND status = 1 ORDER BY sort ASC LIMIT 1";
|
||||
$stmt = \app\util\Db::connection()->getPdo()->prepare($sql);
|
||||
$stmt->execute([$this->id]);
|
||||
return $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有飞书应用配置
|
||||
*/
|
||||
public function getFeishuApps(): array
|
||||
{
|
||||
$sql = "SELECT * FROM company_feishu_apps WHERE company_id = ? AND status = 1 ORDER BY sort";
|
||||
$stmt = \app\util\Db::connection()->getPdo()->prepare($sql);
|
||||
$stmt->execute([$this->id]);
|
||||
return $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
App\Bootstrap\Database::class,
|
||||
];
|
||||
@@ -5,7 +5,7 @@ return [
|
||||
'connections' => [
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'host' => getenv('DB_HOST') ?: 'mysql',
|
||||
'host' => getenv('DB_HOST') ?: '1Panel-mysql-409s',
|
||||
'port' => getenv('DB_PORT') ?: 3306,
|
||||
'database' => getenv('DB_DATABASE') ?: 'xserver',
|
||||
'username' => getenv('DB_USERNAME') ?: 'root',
|
||||
|
||||
+25
-23
@@ -4,35 +4,37 @@ 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::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::post('/user/switch-company', [app\controller\UserController::class, 'switchCompany']);
|
||||
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/submit', [app\controller\ApprovalController::class, 'submit']);
|
||||
// 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('/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/apply', [app\controller\BusinessTripController::class, 'apply']);
|
||||
// Route::put('/business-trip/{id}', [app\controller\BusinessTripController::class, 'update']);
|
||||
// });
|
||||
// 出差审批路由(需要认证)
|
||||
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']);
|
||||
});
|
||||
});
|
||||
|
||||
// 默认路由
|
||||
|
||||
Reference in New Issue
Block a user