diff --git a/backend/app/controller/BusinessTripController.php b/backend/app/controller/BusinessTripController.php index ed03017..9329e70 100644 --- a/backend/app/controller/BusinessTripController.php +++ b/backend/app/controller/BusinessTripController.php @@ -205,4 +205,19 @@ class BusinessTripController extends BaseController return $this->error($e->getMessage()); } } + + /** + * 获取默认审批人和抄送人 + */ + public function getDefaultApprovers(Request $request): Response + { + $userId = $this->getUserId($request); + + try { + $result = $this->service->getDefaultApprovers($userId); + return $this->success($result); + } catch (\Exception $e) { + return $this->error($e->getMessage()); + } + } } diff --git a/backend/app/service/BusinessTripService.php b/backend/app/service/BusinessTripService.php index ae7fca8..3301f58 100644 --- a/backend/app/service/BusinessTripService.php +++ b/backend/app/service/BusinessTripService.php @@ -457,4 +457,71 @@ class BusinessTripService return $businessTrip->fresh(['itineraries', 'approvalOrder']); }); } + + /** + * 获取默认审批人和抄送人 + * 从审批流程配置表中获取 + */ + public function getDefaultApprovers(int $userId): array + { + // 获取出差审批流程 + $flow = ApprovalFlow::where('business_type', 'business_trip') + ->where('status', 1) + ->first(); + + if (!$flow) { + return [ + 'approvers' => [], + 'cc_users' => [], + ]; + } + + // 获取流程节点 + $nodes = ApprovalNode::where('flow_id', $flow->id) + ->orderBy('order') + ->get(); + + $approvers = []; + $ccUsers = []; + + foreach ($nodes as $node) { + $nodeApprovers = $node->approvers ?? []; + + if (empty($nodeApprovers)) { + continue; + } + + // 根据节点类型获取用户信息 + $users = User::whereIn('id', $nodeApprovers) + ->where('status', 1) + ->get(); + + $userList = []; + foreach ($nodeApprovers as $userId) { + $user = $users->firstWhere('id', $userId); + if ($user) { + $userList[] = [ + 'id' => $user->id, + 'name' => $user->name, + 'avatar' => $user->avatar, + 'position' => $user->position, + ]; + } + } + + // 审批节点(approver, and_approve, or_approve)作为审批人 + if ($node->isApprovalNode()) { + $approvers = array_merge($approvers, $userList); + } + // 通知节点(notify)作为抄送人 + elseif ($node->type === ApprovalNode::TYPE_NOTIFY) { + $ccUsers = array_merge($ccUsers, $userList); + } + } + + return [ + 'approvers' => $approvers, + 'cc_users' => $ccUsers, + ]; + } } diff --git a/backend/config/route.php b/backend/config/route.php index 8cb6c5a..7cb97b3 100644 --- a/backend/config/route.php +++ b/backend/config/route.php @@ -34,6 +34,7 @@ Route::group('/api/v1/approval', function () { Route::group('/api/v1/business-trip', function () { Route::get('/list', [app\controller\BusinessTripController::class, 'list']); Route::get('/detail/{id}', [app\controller\BusinessTripController::class, 'detail']); + Route::get('/default-approvers', [app\controller\BusinessTripController::class, 'getDefaultApprovers']); Route::post('/create', [app\controller\BusinessTripController::class, 'create']); Route::post('/update/{id}', [app\controller\BusinessTripController::class, 'update']); Route::post('/submit/{id}', [app\controller\BusinessTripController::class, 'submit']); diff --git a/backend/database/migrations/009_add_default_approval_nodes.sql b/backend/database/migrations/009_add_default_approval_nodes.sql new file mode 100644 index 0000000..b551e2e --- /dev/null +++ b/backend/database/migrations/009_add_default_approval_nodes.sql @@ -0,0 +1,24 @@ +-- 添加默认出差审批流程节点 +-- 1个审批人 + 2个抄送人 + +USE xserver; + +-- 清空 business_trip 流程的现有节点 +DELETE FROM approval_node WHERE flow_id = 2; + +-- 添加审批节点(用户ID 2: 闫继光) +INSERT INTO approval_node (flow_id, type, name, `order`, approvers) VALUES +(2, "approver", "主管审批", 1, '[2]'); + +-- 添加抄送节点(用户ID 3: 孟启, 5: 程丽敏) +INSERT INTO approval_node (flow_id, type, name, `order`, approvers) VALUES +(2, "notify", "抄送人-孟启", 2, '[3]'), +(2, "notify", "抄送人-程丽敏", 3, '[5]'); + +-- 验证结果 +SELECT "=== 默认审批流程节点 ===" AS ""; +SELECT an.id, an.type, an.name, an.`order`, an.approvers, u.name AS approver_name +FROM approval_node an +LEFT JOIN users u ON JSON_CONTAINS(an.approvers, CAST(u.id AS JSON)) +WHERE an.flow_id = 2 +ORDER BY an.`order`; diff --git a/backend/support/Bootstrap.php b/backend/support/Bootstrap.php deleted file mode 100644 index 6b46ec8..0000000 --- a/backend/support/Bootstrap.php +++ /dev/null @@ -1,16 +0,0 @@ - $connection) { - self::$capsule->addConnection($connection, $name); - } - - self::$capsule->setAsGlobal(); - self::$capsule->bootEloquent(); - - if ($default = $config['default'] ?? null) { - self::$capsule->getDatabaseManager()->setDefaultConnection($default); - } - } - - /** - * 获取连接 - */ - public static function connection(?string $name = null): \Illuminate\Database\Connection - { - if (self::$capsule === null) { - self::init(); - } - return self::$capsule->connection($name); - } - - /** - * 获取表查询构造器 - */ - public static function table(string $table, ?string $connection = null): \Illuminate\Database\Query\Builder - { - return self::connection($connection)->table($table); - } - - /** - * 原始SQL表达式 - */ - public static function raw(string $value): \Illuminate\Database\Query\Expression - { - return self::connection()->raw($value); - } - - /** - * 获取 Capsule 实例 - */ - public static function getCapsule(): Capsule - { - if (self::$capsule === null) { - self::init(); - } - return self::$capsule; - } -} diff --git a/backend/support/Model.php b/backend/support/Model.php deleted file mode 100644 index 1cb5898..0000000 --- a/backend/support/Model.php +++ /dev/null @@ -1,16 +0,0 @@ -getConnectionName()); - } -} diff --git a/frontend/src/pages/business-trip/apply.tsx b/frontend/src/pages/business-trip/apply.tsx index eb4ec2f..3426589 100644 --- a/frontend/src/pages/business-trip/apply.tsx +++ b/frontend/src/pages/business-trip/apply.tsx @@ -4,7 +4,8 @@ import { useTranslation } from 'react-i18next' import { DatePicker, Input, Toast, Dialog, TextArea, Selector } from 'antd-mobile' import { AddOutline, DeleteOutline } from 'antd-mobile-icons' import dayjs from 'dayjs' -import { businessTripApi } from '../../services/api' +import { businessTripApi, userApi } from '../../services/api' +import { useUserStore } from '@/stores/user' import styles from './apply.module.css' interface ItineraryItem { @@ -64,6 +65,7 @@ export default function BusinessTripApply() { ]) const [approvers, setApprovers] = useState([]) const [ccUsers, setCcUsers] = useState([]) + const [defaultLoaded, setDefaultLoaded] = useState(false) // 手动保存草稿的函数 const saveDraft = () => { @@ -84,6 +86,40 @@ export default function BusinessTripApply() { // 使用 ref 来跟踪是否已经恢复过草稿 const hasRestoredRef = useRef(false) + // 加载默认审批人和抄送人(仅在首次加载且没有草稿数据时) + useEffect(() => { + const loadDefaultApprovers = async () => { + // 如果已经有存储的草稿数据,不加载默认值 + if (sessionStorage.getItem('businessTripDraft')) { + return + } + // 如果已经从选择页面返回,不加载默认值 + if (location.state?.fromSelection) { + return + } + // 如果已经有存储的人员数据,不加载默认值 + if (sessionStorage.getItem('selectedApprovers') || sessionStorage.getItem('selectedCc')) { + return + } + + try { + const defaultData = await businessTripApi.getDefaultApprovers() + if (defaultData.approvers && defaultData.approvers.length > 0) { + setApprovers(defaultData.approvers) + sessionStorage.setItem('selectedApprovers', JSON.stringify(defaultData.approvers)) + } + if (defaultData.cc_users && defaultData.cc_users.length > 0) { + setCcUsers(defaultData.cc_users) + sessionStorage.setItem('selectedCc', JSON.stringify(defaultData.cc_users)) + } + } catch (error) { + console.error('Failed to load default approvers:', error) + } + } + + loadDefaultApprovers() + }, []) + // 处理从选择页面返回的数据和草稿恢复 useEffect(() => { const fromSelection = location.state?.fromSelection diff --git a/frontend/src/pages/business-trip/select-approver.tsx b/frontend/src/pages/business-trip/select-approver.tsx index 905fdf8..869e8d1 100644 --- a/frontend/src/pages/business-trip/select-approver.tsx +++ b/frontend/src/pages/business-trip/select-approver.tsx @@ -3,7 +3,7 @@ import { useNavigate, useLocation } from 'react-router-dom' import { useTranslation } from 'react-i18next' import { NavBar, SearchBar, Button, Toast, Empty } from 'antd-mobile' import { CheckOutline } from 'antd-mobile-icons' -import { userApi } from '../../services/api' +import { userApi, businessTripApi } from '../../services/api' import styles from './select-approver.module.css' interface UserItem { @@ -34,16 +34,45 @@ export default function SelectUserPage() { const state = location.state as LocationState const [searchValue, setSearchValue] = useState('') - // 只从 sessionStorage 获取对应类型的数据 - const getInitialSelected = () => { - const data = sessionStorage.getItem('selectedApprovers') - return data ? JSON.parse(data) : [] - } - const [selectedUsers, setSelectedUsers] = useState(getInitialSelected()) + const [selectedUsers, setSelectedUsers] = useState([]) const [allUsers, setAllUsers] = useState([]) const [loading, setLoading] = useState(false) const [hasMore, setHasMore] = useState(true) const [page, setPage] = useState(1) + const [defaultLoaded, setDefaultLoaded] = useState(false) + + // 获取初始选中的用户(从 sessionStorage 或后端默认配置) + const loadInitialSelected = async () => { + const type = location.state?.type || 'approver' + const storageKey = type === 'approver' ? 'selectedApprovers' : 'selectedCc' + + // 先尝试从 sessionStorage 获取 + const storedData = sessionStorage.getItem(storageKey) + if (storedData) { + try { + const parsed = JSON.parse(storedData) + if (parsed.length > 0) { + setSelectedUsers(parsed) + return + } + } catch (e) { + console.error('Failed to parse stored data:', e) + } + } + + // 如果没有存储的数据,从后端获取默认配置 + try { + const defaultData = await businessTripApi.getDefaultApprovers() + const defaultUsers = type === 'approver' + ? (defaultData.approvers || []) + : (defaultData.cc_users || []) + setSelectedUsers(defaultUsers) + } catch (error: any) { + console.error('Failed to load default approvers:', error) + // 获取失败时设置为空数组 + setSelectedUsers([]) + } + } // 加载用户列表 const loadUsers = async (pageNum: number = 1, isRefresh: boolean = false) => { @@ -89,6 +118,7 @@ export default function SelectUserPage() { // 初始加载 useEffect(() => { loadUsers(1, true) + loadInitialSelected() }, []) const isUserSelected = (userId: number) => { diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 529445c..f9f099f 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -72,6 +72,9 @@ export const businessTripApi = { getDetail: (id: number) => api.get(`/business-trip/detail/${id}`), + // 获取默认审批人和抄送人 + getDefaultApprovers: () => api.get('/business-trip/default-approvers'), + create: (data: any) => api.post('/business-trip/create', data), update: (id: number, data: any) =>