fix: 修复审批和抄送人相关问题
1. 修复抄送人显示问题 - select-cc.tsx: 添加从后端获取默认抄送人的逻辑 - 之前只从 sessionStorage 获取,导致默认抄送人无法加载 2. 修复多人审批重复操作问题 - ApprovalService: approve() 方法添加重复审批检查 - 检查用户是否已在当前节点审批过,避免重复操作 - 恢复 isNodeCompleted() 从 form_data 获取审批人列表 3. 审批逻辑优化 - 从 form_data['approvers'] 获取用户实际选择的审批人 - 支持会签(and_approve)多人审批场景 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -87,6 +87,18 @@ class ApprovalService
|
|||||||
throw new \Exception('当前节点不存在');
|
throw new \Exception('当前节点不存在');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查当前用户是否已经在当前节点审批过
|
||||||
|
$existingRecord = ApprovalRecord::where('order_id', $order->id)
|
||||||
|
->where('node_id', $currentNode->id)
|
||||||
|
->where('approver_id', $approverId)
|
||||||
|
->whereIn('action', [ApprovalRecord::ACTION_APPROVE, ApprovalRecord::ACTION_REJECT])
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existingRecord) {
|
||||||
|
$actionText = $existingRecord->action === ApprovalRecord::ACTION_APPROVE ? '通过' : '拒绝';
|
||||||
|
throw new \Exception("您已经{$actionText}过此申请,无需重复操作");
|
||||||
|
}
|
||||||
|
|
||||||
// 记录审批操作
|
// 记录审批操作
|
||||||
ApprovalRecord::create([
|
ApprovalRecord::create([
|
||||||
'order_id' => $order->id,
|
'order_id' => $order->id,
|
||||||
@@ -331,8 +343,10 @@ class ApprovalService
|
|||||||
->where('action', 'approve')
|
->where('action', 'approve')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
// 从节点的 approvers 字段获取审批人列表
|
// 从 form_data 获取实际审批人列表(用户可能重新选择过)
|
||||||
$approvers = $node->approvers ?? [];
|
$order = ApprovalOrder::find($orderId);
|
||||||
|
$formData = $order->form_data ?? [];
|
||||||
|
$approvers = $formData['approvers'] ?? [];
|
||||||
|
|
||||||
// 兼容两种格式:[{id:1, name:'...'}] 或 [1]
|
// 兼容两种格式:[{id:1, name:'...'}] 或 [1]
|
||||||
if (!empty($approvers) && is_array($approvers[0] ?? null) && isset($approvers[0]['id'])) {
|
if (!empty($approvers) && is_array($approvers[0] ?? null) && isset($approvers[0]['id'])) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { useNavigate, useLocation } from 'react-router-dom'
|
import { useNavigate, useLocation } from 'react-router-dom'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { NavBar, SearchBar, Button, Toast, Empty } from 'antd-mobile'
|
import { NavBar, SearchBar, Button, Toast, Empty } from 'antd-mobile'
|
||||||
import { CheckOutline } from 'antd-mobile-icons'
|
import { CheckOutline } from 'antd-mobile-icons'
|
||||||
import { userApi } from '../../services/api'
|
import { userApi, businessTripApi } from '../../services/api'
|
||||||
import styles from './select-cc.module.css'
|
import styles from './select-cc.module.css'
|
||||||
|
|
||||||
interface UserItem {
|
interface UserItem {
|
||||||
@@ -34,16 +34,46 @@ export default function SelectCcPage() {
|
|||||||
const state = location.state as LocationState
|
const state = location.state as LocationState
|
||||||
|
|
||||||
const [searchValue, setSearchValue] = useState('')
|
const [searchValue, setSearchValue] = useState('')
|
||||||
// 只从 sessionStorage 获取对应类型的数据
|
const [selectedUsers, setSelectedUsers] = useState<UserItem[]>([])
|
||||||
const getInitialSelected = () => {
|
|
||||||
const data = sessionStorage.getItem('selectedCc')
|
|
||||||
return data ? JSON.parse(data) : []
|
|
||||||
}
|
|
||||||
const [selectedUsers, setSelectedUsers] = useState<UserItem[]>(getInitialSelected())
|
|
||||||
const [allUsers, setAllUsers] = useState<UserItem[]>([])
|
const [allUsers, setAllUsers] = useState<UserItem[]>([])
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [hasMore, setHasMore] = useState(true)
|
const [hasMore, setHasMore] = useState(true)
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
|
const initialLoadedRef = useRef(false)
|
||||||
|
|
||||||
|
// 获取初始选中的用户(从 sessionStorage 或后端默认配置)
|
||||||
|
const loadInitialSelected = async () => {
|
||||||
|
// 防止重复加载
|
||||||
|
if (initialLoadedRef.current) return
|
||||||
|
|
||||||
|
// 先尝试从 sessionStorage 获取
|
||||||
|
const storedData = sessionStorage.getItem('selectedCc')
|
||||||
|
if (storedData) {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(storedData)
|
||||||
|
setSelectedUsers(parsed)
|
||||||
|
initialLoadedRef.current = true
|
||||||
|
return
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse stored data:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有存储的数据,从后端获取默认配置
|
||||||
|
try {
|
||||||
|
const defaultData = await businessTripApi.getDefaultApprovers()
|
||||||
|
const defaultUsers = defaultData.cc_users || []
|
||||||
|
setSelectedUsers(defaultUsers)
|
||||||
|
// 将默认数据也保存到 sessionStorage
|
||||||
|
if (defaultUsers.length > 0) {
|
||||||
|
sessionStorage.setItem('selectedCc', JSON.stringify(defaultUsers))
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Failed to load default cc users:', error)
|
||||||
|
setSelectedUsers([])
|
||||||
|
}
|
||||||
|
initialLoadedRef.current = true
|
||||||
|
}
|
||||||
|
|
||||||
// 加载用户列表
|
// 加载用户列表
|
||||||
const loadUsers = async (pageNum: number = 1, isRefresh: boolean = false) => {
|
const loadUsers = async (pageNum: number = 1, isRefresh: boolean = false) => {
|
||||||
@@ -89,6 +119,7 @@ export default function SelectCcPage() {
|
|||||||
// 初始加载
|
// 初始加载
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadUsers(1, true)
|
loadUsers(1, true)
|
||||||
|
loadInitialSelected()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const isUserSelected = (userId: number) => {
|
const isUserSelected = (userId: number) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user