fix: 修复审批后消息卡片更新和多行程显示问题
1. 审批人审批后,自己的消息卡片也更新为显示查看详情按钮 - 新增 updateActorMessageCard() 方法更新操作人自己的卡片 - 审批通过/拒绝时同时更新操作人和其他审批人的卡片 2. 多行程路线在审批详情中正确显示 - 特殊处理 itineraries 字段,展示每个行程的详细信息 - 添加行程列表样式,包括目的地、日期、费用等 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -134,6 +134,16 @@ class ApprovalService
|
||||
$approvedCount = count($approvedRecords);
|
||||
|
||||
if ($this->notificationService) {
|
||||
// 更新操作人自己的消息卡片
|
||||
$this->notificationService->setAppByUser($approverId);
|
||||
$this->notificationService->updateActorMessageCard(
|
||||
$order->id,
|
||||
$currentNode->id,
|
||||
$approverId,
|
||||
'approved'
|
||||
);
|
||||
|
||||
// 更新其他审批人的消息卡片
|
||||
$this->notificationService->updateMessageCardsForPartialApprove(
|
||||
$order->id,
|
||||
$currentNode->id,
|
||||
@@ -152,6 +162,16 @@ class ApprovalService
|
||||
$approverName = $approver ? $approver->name : '未知';
|
||||
|
||||
if ($this->notificationService) {
|
||||
// 更新操作人自己的消息卡片
|
||||
$this->notificationService->setAppByUser($approverId);
|
||||
$this->notificationService->updateActorMessageCard(
|
||||
$order->id,
|
||||
$currentNode->id,
|
||||
$approverId,
|
||||
'approved'
|
||||
);
|
||||
|
||||
// 更新其他审批人的消息卡片
|
||||
$this->notificationService->updateMessageCards(
|
||||
$order->id,
|
||||
$currentNode->id,
|
||||
@@ -239,6 +259,16 @@ class ApprovalService
|
||||
}
|
||||
|
||||
if ($currentNode) {
|
||||
// 更新操作人自己的消息卡片
|
||||
$this->notificationService->setAppByUser($approverId);
|
||||
$this->notificationService->updateActorMessageCard(
|
||||
$order->id,
|
||||
$currentNode->id,
|
||||
$approverId,
|
||||
'rejected'
|
||||
);
|
||||
|
||||
// 更新其他审批人的消息卡片
|
||||
$this->notificationService->updateMessageCards(
|
||||
$order->id,
|
||||
$currentNode->id,
|
||||
|
||||
@@ -670,6 +670,121 @@ class FeishuNotificationService
|
||||
* @param string $operatorName 操作人姓名
|
||||
* @return bool
|
||||
*/
|
||||
/**
|
||||
* 更新操作人自己的消息卡片(审批完成后显示查看详情按钮)
|
||||
*
|
||||
* @param int $orderId 订单ID
|
||||
* @param int $nodeId 节点ID
|
||||
* @param int $actorUserId 操作人ID(刚刚完成审批的用户)
|
||||
* @param string $status 审批状态 (approved/rejected)
|
||||
* @return bool
|
||||
*/
|
||||
public function updateActorMessageCard(int $orderId, int $nodeId, int $actorUserId, string $status): bool
|
||||
{
|
||||
try {
|
||||
// 获取该操作人的消息
|
||||
$message = \app\model\FeishuMessage::where('order_id', $orderId)
|
||||
->where('node_id', $nodeId)
|
||||
->where('user_id', $actorUserId)
|
||||
->where('status', \app\model\FeishuMessage::STATUS_ACTIVE)
|
||||
->first();
|
||||
|
||||
if (!$message) {
|
||||
error_log("📭 没有找到操作人的消息: order_id={$orderId}, node_id={$nodeId}, user_id={$actorUserId}");
|
||||
return true;
|
||||
}
|
||||
|
||||
$companyId = $message->company_id;
|
||||
$feishuAppId = $message->feishu_app_id;
|
||||
|
||||
if (!$companyId || !$feishuAppId) {
|
||||
error_log("❌ 消息记录缺少 company_id 或 feishu_app_id: message_id={$message->message_id}");
|
||||
return false;
|
||||
}
|
||||
|
||||
error_log("🔍 准备更新操作人消息: message_id={$message->message_id}, company_id={$companyId}, feishu_app_id={$feishuAppId}");
|
||||
|
||||
// 使用该消息对应的飞书应用配置
|
||||
$this->companyId = $companyId;
|
||||
$this->appId = $feishuAppId;
|
||||
|
||||
// 获取订单信息
|
||||
$order = \app\model\ApprovalOrder::find($orderId);
|
||||
$orderTitle = $order ? $order->title : '审批单';
|
||||
|
||||
// 构建操作人自己的卡片(显示操作结果和查看详情按钮)
|
||||
$statusText = $status === 'approved' ? '已通过' : '已拒绝';
|
||||
$statusColor = $status === 'approved' ? 'green' : 'red';
|
||||
$statusEmoji = $status === 'approved' ? '✅' : '❌';
|
||||
|
||||
$updatedCard = [
|
||||
'config' => [
|
||||
'wide_screen_mode' => false,
|
||||
],
|
||||
'header' => [
|
||||
'title' => [
|
||||
'content' => "{$statusEmoji} 已操作",
|
||||
'tag' => 'plain_text',
|
||||
],
|
||||
'template' => $statusColor,
|
||||
],
|
||||
'elements' => [
|
||||
[
|
||||
'tag' => 'div',
|
||||
'text' => [
|
||||
'content' => "**审批单**: {$orderTitle}\n\n**您的操作**: {$statusText}\n\n您已完成此审批操作。",
|
||||
'tag' => 'lark_md',
|
||||
],
|
||||
],
|
||||
[
|
||||
'tag' => 'action',
|
||||
'actions' => [
|
||||
[
|
||||
'tag' => 'button',
|
||||
'text' => [
|
||||
'content' => '查看详情',
|
||||
'tag' => 'plain_text',
|
||||
],
|
||||
'type' => 'default',
|
||||
'url' => "https://uniservice-mobile.hnxinyouhe.com/approval/detail/{$orderId}?company={$companyId}",
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$contentJson = json_encode($updatedCard, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
$response = HttpClient::httpPatchJson(
|
||||
"https://open.feishu.cn/open-apis/im/v1/messages/{$message->message_id}",
|
||||
[
|
||||
'msg_type' => 'interactive',
|
||||
'content' => $contentJson,
|
||||
],
|
||||
['content'],
|
||||
[
|
||||
'Authorization: Bearer ' . $this->getAccessToken(),
|
||||
]
|
||||
);
|
||||
|
||||
$code = $response['code'] ?? -1;
|
||||
if ($code === 0) {
|
||||
error_log("✅ 操作人消息卡片更新成功: message_id={$message->message_id}, user_id={$actorUserId}, status={$status}");
|
||||
// 标记消息为已更新
|
||||
$message->status = \app\model\FeishuMessage::STATUS_UPDATED;
|
||||
$message->save();
|
||||
} else {
|
||||
error_log("❌ 操作人消息卡片更新失败: message_id={$message->message_id}, error=" . json_encode($response));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (Exception $e) {
|
||||
error_log("❌ updateActorMessageCard 异常: " . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新其他审批人的消息卡片
|
||||
* 多公司多飞书应用场景:使用每个消息对应的原始飞书应用去更新
|
||||
|
||||
@@ -115,6 +115,56 @@
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.formSection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.itineraryList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.itineraryItem {
|
||||
background: #fafafa;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
border: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.itineraryHeader {
|
||||
margin-bottom: 8px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed #e8e8e8;
|
||||
}
|
||||
|
||||
.itineraryIndex {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.itineraryRow {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.itineraryLabel {
|
||||
color: #666;
|
||||
width: 80px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.itineraryValue {
|
||||
color: #333;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.flowNodes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -600,6 +600,47 @@ export default function ApprovalDetail() {
|
||||
.map(([key, value]) => {
|
||||
// 获取字段标签,优先使用映射表,没有则使用原始key
|
||||
const label = formFieldLabelMap[key] || key
|
||||
|
||||
// 特殊处理行程安排(itineraries)
|
||||
if (key === 'itineraries' && Array.isArray(value) && value.length > 0) {
|
||||
return (
|
||||
<div key={key} className={styles.formSection}>
|
||||
<div className={styles.formLabel}>{label}:</div>
|
||||
<div className={styles.itineraryList}>
|
||||
{value.map((itinerary: any, index: number) => (
|
||||
<div key={index} className={styles.itineraryItem}>
|
||||
<div className={styles.itineraryHeader}>
|
||||
<span className={styles.itineraryIndex}>行程 {index + 1}</span>
|
||||
</div>
|
||||
{Object.entries(itinerary).map(([ikey, ivalue]) => {
|
||||
if (ikey === 'id') return null
|
||||
const itineraryLabelMap: Record<string, string> = {
|
||||
destination: '目的地',
|
||||
start_date: '开始日期',
|
||||
end_date: '结束日期',
|
||||
estimated_cost: '预计费用',
|
||||
}
|
||||
const itineraryLabel = itineraryLabelMap[ikey] || ikey
|
||||
let displayValue = ivalue as string
|
||||
if (ikey.includes('date') && ivalue) {
|
||||
displayValue = dayjs(String(ivalue)).format('YYYY-MM-DD')
|
||||
} else if (ikey === 'estimated_cost' && ivalue) {
|
||||
displayValue = `¥${Number(ivalue).toFixed(2)}`
|
||||
}
|
||||
return (
|
||||
<div key={ikey} className={styles.itineraryRow}>
|
||||
<span className={styles.itineraryLabel}>{itineraryLabel}:</span>
|
||||
<span className={styles.itineraryValue}>{displayValue}</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={key} className={styles.formRow}>
|
||||
<span className={styles.formLabel}>{label}:</span>
|
||||
|
||||
Reference in New Issue
Block a user