feat: 飞书审批消息添加操作按钮
功能: 1. 审批通知卡片添加"通过"和"拒绝"按钮 2. 点击按钮跳转到审批详情页,自动弹出审批确认框 3. 保留"查看详情"按钮 实现: - FeishuNotificationService.buildApprovalCard() - 添加审批操作按钮 - ApprovalService.notifyNextApprovers() - 传递 order_id 参数 - approval/detail.tsx - 处理 URL 中的 action 参数 按钮样式: - 通过按钮:绿色 (primary) - 拒绝按钮:红色 (danger) - 查看详情:默认样式 相关文件: - backend/app/service/FeishuNotificationService.php - backend/app/service/ApprovalService.php - frontend/src/pages/approval/detail.tsx Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -482,6 +482,7 @@ class ApprovalService
|
|||||||
'type' => $businessTypeName,
|
'type' => $businessTypeName,
|
||||||
'content' => "**申请标题**: {$order->title}\n**申请人**: {$order->applicant->name}",
|
'content' => "**申请标题**: {$order->title}\n**申请人**: {$order->applicant->name}",
|
||||||
'url' => $detailUrl,
|
'url' => $detailUrl,
|
||||||
|
'order_id' => $order->id,
|
||||||
]);
|
]);
|
||||||
error_log("✅ 审批人通知成功: approver_id={$approverId}");
|
error_log("✅ 审批人通知成功: approver_id={$approverId}");
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -118,8 +118,7 @@ class FeishuNotificationService
|
|||||||
* - type: 申请类型
|
* - type: 申请类型
|
||||||
* - content: 申请内容摘要
|
* - content: 申请内容摘要
|
||||||
* - url: 详情链接
|
* - url: 详情链接
|
||||||
* - approve_url: 通过按钮链接(可选)
|
* - order_id: 审批单ID(用于构建审批操作按钮)
|
||||||
* - reject_url: 拒绝按钮链接(可选)
|
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
public function sendApprovalNotification(array $params): bool
|
public function sendApprovalNotification(array $params): bool
|
||||||
@@ -284,10 +283,12 @@ class FeishuNotificationService
|
|||||||
$type = $params['type'] ?? '申请';
|
$type = $params['type'] ?? '申请';
|
||||||
$content = $params['content'] ?? '';
|
$content = $params['content'] ?? '';
|
||||||
$url = $params['url'] ?? '';
|
$url = $params['url'] ?? '';
|
||||||
|
$orderId = $params['order_id'] ?? null;
|
||||||
|
|
||||||
// 简化版卡片,先测试
|
// 构建卡片元素
|
||||||
$elements = [];
|
$elements = [];
|
||||||
|
|
||||||
|
// 内容区域
|
||||||
if ($content) {
|
if ($content) {
|
||||||
$elements[] = [
|
$elements[] = [
|
||||||
'tag' => 'div',
|
'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) {
|
if ($url) {
|
||||||
$elements[] = [
|
$actions[] = [
|
||||||
'tag' => 'action',
|
|
||||||
'actions' => [
|
|
||||||
[
|
|
||||||
'tag' => 'button',
|
'tag' => 'button',
|
||||||
'text' => [
|
'text' => [
|
||||||
'content' => '查看详情',
|
'content' => '查看详情',
|
||||||
'tag' => 'plain_text',
|
'tag' => 'plain_text',
|
||||||
],
|
],
|
||||||
'type' => 'default',
|
'type' => 'default',
|
||||||
|
'size' => 'medium',
|
||||||
'url' => $url,
|
'url' => $url,
|
||||||
],
|
];
|
||||||
],
|
}
|
||||||
|
|
||||||
|
if (!empty($actions)) {
|
||||||
|
$elements[] = [
|
||||||
|
'tag' => 'action',
|
||||||
|
'actions' => $actions,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react'
|
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 { useTranslation } from 'react-i18next'
|
||||||
import { Card, Tag, Button, Dialog, Toast, TextArea } from 'antd-mobile'
|
import { Card, Tag, Button, Dialog, Toast, TextArea } from 'antd-mobile'
|
||||||
import {
|
import {
|
||||||
@@ -98,6 +98,7 @@ export default function ApprovalDetail() {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
|
const location = useLocation()
|
||||||
const { user } = useUserStore()
|
const { user } = useUserStore()
|
||||||
|
|
||||||
const [detail, setDetail] = useState<ApprovalDetail | null>(null)
|
const [detail, setDetail] = useState<ApprovalDetail | null>(null)
|
||||||
@@ -126,6 +127,24 @@ export default function ApprovalDetail() {
|
|||||||
loadDetail()
|
loadDetail()
|
||||||
}, [id])
|
}, [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 = () => {
|
const handleCancel = () => {
|
||||||
Dialog.confirm({
|
Dialog.confirm({
|
||||||
|
|||||||
Reference in New Issue
Block a user