diff --git a/backend/app/service/ApprovalService.php b/backend/app/service/ApprovalService.php index 82d786f..40a2a85 100644 --- a/backend/app/service/ApprovalService.php +++ b/backend/app/service/ApprovalService.php @@ -482,6 +482,7 @@ class ApprovalService 'type' => $businessTypeName, 'content' => "**申请标题**: {$order->title}\n**申请人**: {$order->applicant->name}", 'url' => $detailUrl, + 'order_id' => $order->id, ]); error_log("✅ 审批人通知成功: approver_id={$approverId}"); } catch (\Exception $e) { diff --git a/backend/app/service/FeishuNotificationService.php b/backend/app/service/FeishuNotificationService.php index 32650b9..7fcac3a 100644 --- a/backend/app/service/FeishuNotificationService.php +++ b/backend/app/service/FeishuNotificationService.php @@ -118,8 +118,7 @@ class FeishuNotificationService * - type: 申请类型 * - content: 申请内容摘要 * - url: 详情链接 - * - approve_url: 通过按钮链接(可选) - * - reject_url: 拒绝按钮链接(可选) + * - order_id: 审批单ID(用于构建审批操作按钮) * @return bool */ public function sendApprovalNotification(array $params): bool @@ -284,10 +283,12 @@ class FeishuNotificationService $type = $params['type'] ?? '申请'; $content = $params['content'] ?? ''; $url = $params['url'] ?? ''; + $orderId = $params['order_id'] ?? null; - // 简化版卡片,先测试 + // 构建卡片元素 $elements = []; + // 内容区域 if ($content) { $elements[] = [ 'tag' => 'div', @@ -306,20 +307,54 @@ class FeishuNotificationService ]; } + // 操作按钮区域 + $actions = []; + + // 如果有审批单ID,添加审批操作按钮 + if ($orderId) { + // 通过按钮(绿色) + $actions[] = [ + 'tag' => 'button', + 'text' => [ + 'content' => '✓ 通过', + 'tag' => 'plain_text', + ], + 'type' => 'primary', + 'size' => 'medium', + 'url' => "https://xsm.dev.hnxinyouhe.com/approval/detail/{$orderId}?action=approve", + ]; + + // 拒绝按钮(红色) + $actions[] = [ + 'tag' => 'button', + 'text' => [ + 'content' => '✗ 拒绝', + 'tag' => 'plain_text', + ], + 'type' => 'danger', + 'size' => 'medium', + 'url' => "https://xsm.dev.hnxinyouhe.com/approval/detail/{$orderId}?action=reject", + ]; + } + + // 查看详情按钮 if ($url) { + $actions[] = [ + 'tag' => 'button', + 'text' => [ + 'content' => '查看详情', + 'tag' => 'plain_text', + ], + 'type' => 'default', + 'size' => 'medium', + 'url' => $url, + ]; + } + + if (!empty($actions)) { $elements[] = [ 'tag' => 'action', - 'actions' => [ - [ - 'tag' => 'button', - 'text' => [ - 'content' => '查看详情', - 'tag' => 'plain_text', - ], - 'type' => 'default', - 'url' => $url, - ], - ], + 'actions' => $actions, ]; } diff --git a/frontend/src/pages/approval/detail.tsx b/frontend/src/pages/approval/detail.tsx index b5f9df5..d8555c7 100644 --- a/frontend/src/pages/approval/detail.tsx +++ b/frontend/src/pages/approval/detail.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { useNavigate, useParams } from 'react-router-dom' +import { useNavigate, useParams, useLocation } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { Card, Tag, Button, Dialog, Toast, TextArea } from 'antd-mobile' import { @@ -98,6 +98,7 @@ export default function ApprovalDetail() { const { t } = useTranslation() const navigate = useNavigate() const { id } = useParams() + const location = useLocation() const { user } = useUserStore() const [detail, setDetail] = useState(null) @@ -126,6 +127,24 @@ export default function ApprovalDetail() { loadDetail() }, [id]) + // 处理 URL 中的 action 参数(来自飞书消息按钮) + useEffect(() => { + if (!detail || !canApprove) return + + const searchParams = new URLSearchParams(location.search) + const action = searchParams.get('action') + + if (action === 'approve' || action === 'reject') { + // 自动打开审批对话框 + setActionType(action) + setComment('') + setCommentVisible(true) + + // 清除 URL 参数,避免重复触发 + window.history.replaceState({}, '', `/approval/detail/${id}`) + } + }, [detail, location.search]) + // 撤销申请 const handleCancel = () => { Dialog.confirm({