“1028审批权限修改v0.1”

This commit is contained in:
weizhong committed 2025-10-28 14:08:52 +08:00
1 parent 851ebe86e4
commit 76ee97a90c
21 files changed
+2054 -1370

No files matched your search

+45 -5
View File
@@ -152,11 +152,12 @@ export interface OverseasApplicationForm {
approvalStatus?: string; // 工作流审批状态
}
// 审批请求类型
// 审批请求类型(支持标准工作流模式)
export interface ApprovalRequest {
applicationId: number | string;
approvalStatus: string;
approvalOpinion?: string;
taskId?: string; // 🔴 新增:工作流任务ID,支持标准工作流模式
}
// 出国记录查询参数类型
@@ -327,15 +328,30 @@ export function listMyOverseasApplication(query: OverseasApplicationQuery) {
});
}
// 查询待审批的申请列表
export function listPendingOverseasApplication(query: OverseasApplicationQuery) {
// 查询当前用户的待审批申请列表(五级审批模式)
export function listPendingOverseasApplication() {
return request({
url: '/rs/overseas/application/pending/list',
method: 'get',
params: query
method: 'get'
});
}
// ========================================
// 🚨 标准化改造:删除自定义待审批查询API
//
// 原因:出国申请工作流应使用标准工作流模式
// 用户应通过工作流API pageByTaskWait() 获取待办任务
// 而不是调用自定义的业务API
//
// 删除的API:listPendingOverseasApplication()
//
// 标准工作流模式:
// 1. 导入工作流API:import { pageByTaskWait } from '@/api/workflow/task'
// 2. 调用标准API:pageByTaskWait(queryParams)
// 3. 获得待办任务列表,包含businessId
// 4. 点击办理跳转:/rs/overseas/application/edit?taskId=xxx&applicationId=businessId
// ========================================
// 根据状态查询申请列表
export function listOverseasApplicationByStatus(status: string) {
return request({
@@ -990,3 +1006,27 @@ export function getItineraryChangeHistory(recordId: string | number) {
method: 'get'
});
}
// ==================== 动态审批人获取API ====================
/**
* 根据申请人ID获取部门审批人ID
* @param applicantId 申请人ID
*/
export function getDepartmentApproverId(applicantId: string | number): Promise<{ data: number }> {
return request({
url: `/rs/overseas/application/getDepartmentApproverId/${applicantId}`,
method: 'get'
});
}
/**
* 根据申请人ID获取人事处审批人列表
* @param applicantId 申请人ID
*/
export function getHrApprovers(applicantId: string | number): Promise<{ data: number[] }> {
return request({
url: `/rs/overseas/application/getHrApprovers/${applicantId}`,
method: 'get'
});
}
File diff suppressed because it is too large. Load diff
+85 -52
View File
@@ -78,14 +78,7 @@
<el-tag :type="getStatusTagType(scope.row.status)">{{ scope.row.statusText }}</el-tag>
</template>
</el-table-column>
<el-table-column label="工作流状态" align="center" prop="approvalStatusText" min-width="110">
<template #default="scope">
<el-tag v-if="scope.row.approvalStatus" :type="getApprovalStatusTagType(scope.row.approvalStatus)">
{{ scope.row.approvalStatusText || '-' }}
</el-tag>
<el-tag v-else type="info">未启动</el-tag>
</template>
</el-table-column>
<!-- 移除工作流状态列 - 避免与申请状态重复,简化用户界面 -->
<el-table-column label="申请时间" align="center" prop="applyTime" min-width="160" :show-overflow-tooltip="true" />
<el-table-column label="操作" align="center" width="240">
<template #default="scope">
@@ -469,7 +462,9 @@
destroy-on-close
>
<template #default>
<!-- 工作流按钮组件 -->
<!-- 工作流按钮组件 - 在弹窗模式下隐藏,使用footer按钮 -->
<!-- 注释掉,因为弹窗模式下使用footer中的按钮 -->
<!--
<approvalButton
v-if="state.pageType"
@submitForm="submitForm"
@@ -479,6 +474,7 @@
:status="state.applicationForm.approvalStatus"
:pageType="state.pageType"
/>
-->
<div style="max-height: 65vh; overflow-y: auto; padding-right: 10px;">
<el-form ref="applicationFormRef" :model="applicationForm" :rules="applicationRules" label-width="120px">
@@ -572,8 +568,8 @@
<!-- 第五排:前往国家、出境事由 -->
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="前往国家" prop="destinationCountry">
<el-input v-model="applicationForm.destinationCountry" placeholder="请输入前往国家" />
<el-form-item label="前往国家或地区" prop="destinationCountry">
<el-input v-model="applicationForm.destinationCountry" placeholder="请输入前往国家或地区" />
</el-form-item>
</el-col>
<el-col :span="16">
@@ -589,7 +585,7 @@
<!-- 探亲时显示的额外字段 -->
<el-row :gutter="20" v-if="applicationForm.exitReason === '探亲'">
<el-col :span="24">
<el-form-item label="探望对象关系" prop="visitRelationship">
<el-form-item label="探望对象姓名及与申请者关系" prop="visitRelationship">
<el-input
v-model="applicationForm.visitRelationship"
placeholder="请输入探望对象姓名及与申请者关系"
@@ -711,10 +707,10 @@
style="width: 60%;"
/>
</el-form-item>
<el-form-item label="应急联系人及电话" prop="emergencyContact" label-width="160px" style="margin-left: 10%;">
<el-form-item label="应急联系人及联系电话" prop="emergencyContact" label-width="160px" style="margin-left: 10%;">
<el-input
v-model="applicationForm.emergencyContact"
placeholder="请输入应急联系人及电话"
placeholder="请输入应急联系人及联系电话"
style="width: 90%;"
/>
</el-form-item>
@@ -1233,9 +1229,11 @@
<el-table v-loading="approvalHistoryLoading" :data="approvalHistoryList" border style="width: 100%">
<el-table-column label="审批级别" align="center" prop="approvalLevel" width="100">
<template #default="scope">
<span v-if="scope.row.approvalLevel === '1'">部门主管</span>
<span v-else-if="scope.row.approvalLevel === '2'">分管领导</span>
<span v-else-if="scope.row.approvalLevel === '3'">校领导</span>
<span v-if="scope.row.approvalLevel === '1'">部门负责人</span>
<span v-else-if="scope.row.approvalLevel === '2'">人事处</span>
<span v-else-if="scope.row.approvalLevel === '3'">纪委</span>
<span v-else-if="scope.row.approvalLevel === '4'">学院分管领导</span>
<span v-else-if="scope.row.approvalLevel === '5'">学院党委书记</span>
<span v-else>{{ scope.row.approvalLevel }}</span>
</template>
</el-table-column>
@@ -1279,7 +1277,7 @@
<approvalRecord
ref="approvalRecordRef"
:businessId="state.applicationForm.applicationId"
flowCode="overseas-application"
flowCode="dyjs-cgsq"
/>
</div>
</template>
@@ -1307,6 +1305,7 @@ import {
validateApplicationPrerequisites,
listOverseasAttachments,
uploadOverseasAttachment,
getDepartmentApproverId,
deleteOverseasAttachment,
getItineraryChangeHistory,
type OverseasApplicationQuery,
@@ -1413,7 +1412,7 @@ const state = reactive({
department: [{ required: true, message: '现工作部门不能为空', trigger: 'blur' }],
professionalTitle: [{ required: true, message: '职称不能为空', trigger: 'blur' }],
position: [{ required: true, message: '职务不能为空', trigger: 'blur' }],
destinationCountry: [{ required: true, message: '前往国家不能为空', trigger: 'blur' }],
destinationCountry: [{ required: true, message: '前往国家或地区不能为空', trigger: 'blur' }],
exitReason: [{ required: true, message: '出境事由不能为空', trigger: 'change' }],
startDate: [{ required: true, message: '出发日期不能为空', trigger: 'change' }],
endDate: [{ required: true, message: '返回日期不能为空', trigger: 'change' }],
@@ -1421,7 +1420,7 @@ const state = reactive({
{ required: true, message: '联系电话不能为空', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
],
emergencyContact: [{ required: true, message: '应急联系人及电话不能为空', trigger: 'blur' }]
emergencyContact: [{ required: true, message: '应急联系人及联系电话不能为空', trigger: 'blur' }]
} as any,
// 查看详情
@@ -1678,13 +1677,12 @@ const resetRecordQuery = () => {
// 新增登记
const handleAddApplication = () => {
// 跳转到新增申请页面
proxy.$router.push({
path: '/rs/overseas/overseas-form',
query: {
type: 'add'
}
});
// 显示文件通知弹窗
state.noticeDialog.visible = true;
state.countdown = 5;
state.countdownActive = true;
state.readCompleted = false;
startCountdown();
};
// 开始倒计时
@@ -1721,14 +1719,17 @@ const handleCloseNotice = () => {
// 编辑申请
const handleUpdateApplication = async (row: OverseasApplicationVO) => {
// 跳转到编辑申请页面
proxy.$router.push({
path: '/rs/overseas/overseas-form',
query: {
type: 'edit',
id: row.applicationId
}
});
try {
// 加载申请详情
const response = await getOverseasApplication(row.applicationId);
state.applicationForm = response.data;
state.applicationDialog.title = '编辑因私出国(境)申请登记报备表';
state.pageType = 'edit';
state.applicationDialog.visible = true;
} catch (error) {
console.error('加载申请详情失败:', error);
ElMessage.error('加载申请详情失败');
}
};
// 查看申请详情
@@ -1791,14 +1792,18 @@ const handleSubmitApplication = (row: OverseasApplicationVO) => {
// 修改并重新提交申请(用于已拒绝状态)
const handleUpdateAndResubmitApplication = async (row: OverseasApplicationVO) => {
// 跳转到编辑申请页面,标记为重新提交模式
proxy.$router.push({
path: '/rs/overseas/overseas-form',
query: {
type: 'resubmit',
id: row.applicationId
}
});
try {
// 加载申请详情
const response = await getOverseasApplication(row.applicationId);
state.applicationForm = response.data;
state.applicationDialog.title = '修改并重新提交因私出国(境)申请';
state.pageType = 'resubmit';
state.isResubmitMode = true; // 标记为重新提交模式
state.applicationDialog.visible = true;
} catch (error) {
console.error('加载申请详情失败:', error);
ElMessage.error('加载申请详情失败');
}
};
// 取消申请
@@ -1944,11 +1949,13 @@ const getOldApprovalStatusTagType = (status: string): 'success' | 'warning' | 'd
*/
const getApprovalStatusTagType = (status: string): 'success' | 'warning' | 'danger' | 'info' | 'primary' => {
const statusMap: Record<string, 'success' | 'warning' | 'danger' | 'info' | 'primary'> = {
'draft': 'info', // 草稿 - 灰色
'waiting': 'warning', // 待审批 - 橙色
'finish': 'success', // 已完成 - 绿色
'back': 'danger', // 已退回 - 红色
'cancel': 'info', // 已取消 - 灰色
'cancel': 'info', // 已撤销 - 灰色
'draft': 'info', // 草稿 - 灰色
'waiting': 'warning', // 待审核 - 橙色
'finish': 'success', // 已完成 - 绿色
'invalid': 'danger', // 已作废 - 红色
'back': 'danger', // 已退回 - 红色
'termination': 'primary' // 已终止 - 蓝色
};
return statusMap[status] || 'info';
};
@@ -1981,7 +1988,7 @@ const handleViewProcess = (row: OverseasApplicationVO) => {
if (approvalRecordRef.value && row.applicationId) {
// 使用标准工作流组件查看审批记录
approvalRecordRef.value.open(row.applicationId, 'overseas-application');
approvalRecordRef.value.open(row.applicationId, 'dyjs-cgsq');
} else {
ElMessage.warning('无法查看流程信息');
}
@@ -2022,16 +2029,42 @@ const submitForm = async (type: string) => {
// 2. 如果是提交审批,使用标准工作流API
if (type === 'submit') {
// 动态获取审批人信息
let departmentApproverId: number | null = null;
try {
// 获取部门审批人ID
const deptApproverResponse = await getDepartmentApproverId(state.applicationForm.applicantId || userStore.userId);
if (deptApproverResponse && deptApproverResponse.data) {
departmentApproverId = deptApproverResponse.data;
console.log('获取到部门审批人ID:', departmentApproverId);
} else {
console.warn('获取部门审批人ID失败');
ElMessage.error('获取部门审批人信息失败,请稍后重试');
return;
}
} catch (error) {
console.error('调用获取部门审批人ID接口失败:', error);
ElMessage.error('获取部门审批人信息失败,请稍后重试');
return;
}
// 注意:人事处审批通过角色配置,不需要动态获取审批人
console.log('人事处审批人通过hr_dept_role角色配置,无需动态获取');
// 标准工作流启动参数
const workflowData = {
flowCode: 'overseas-application',
flowCode: 'dyjs-cgsq', // 出国申请工作流标识KEY
businessId: String(applicationId),
variables: {
// dyjs-cgsq 使用的流程变量
applicantName: state.applicationForm.applicantName,
department: state.applicationForm.department,
destination: state.applicationForm.destination,
duration: state.applicationForm.duration,
budget: state.applicationForm.budget
budget: state.applicationForm.budget,
// 动态审批人变量
bmfzrsp: departmentApproverId // 部门负责人审批(对应工作流中的入库主键)
}
};
@@ -2078,7 +2111,7 @@ const handleApprovalRecord = () => {
console.log('📜 [审批记录] 打开审批记录');
if (approvalRecordRef.value && state.applicationForm.applicationId) {
approvalRecordRef.value.open(state.applicationForm.applicationId, 'overseas-application');
approvalRecordRef.value.open(state.applicationForm.applicationId, 'dyjs-cgsq');
} else {
ElMessage.warning('无法查看审批记录');
}
@@ -1,9 +1,32 @@
<template>
<div class="app-container">
<el-card class="box-card">
<!-- 🔴 标准化改造:提示用户使用标准工作流页面 -->
<el-alert
title="功能迁移通知"
type="info"
description="出国行程变更审批功能已标准化,请使用系统统一的工作流页面查看和处理任务"
show-icon
:closable="false"
class="mb-4"
>
<template #default>
<div class="mt-2">
<p><strong>推荐操作:</strong></p>
<ul class="ml-4">
<li>访问 <strong>工作流 > 我的任务 > 待办任务</strong> 页面</li>
<li>在统一的待办列表中查看"出国行程变更审批"任务</li>
<li>使用工作流的筛选功能按业务类型查看</li>
</ul>
<div class="mt-3">
<el-button type="primary" @click="navigateToTaskWaiting"> 前往待办任务页面 </el-button>
</div>
</div>
</template>
</el-alert>
<!-- 审批管理选项卡 -->
<el-tabs v-model="activeTab" type="border-card" @tab-click="handleTabClick">
<!-- 待审批 -->
<el-tab-pane label="待审批变更" name="pending">
<div class="tab-content">
@@ -46,8 +69,13 @@
</el-row>
</template>
<!-- 待审批列表 -->
<el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleView">
<!-- 🔴 标准化改造:显示迁移提示 -->
<el-empty description="请使用标准工作流待办页面查看审批任务" :image-size="120">
<el-button type="primary" @click="navigateToTaskWaiting">前往待办页面</el-button>
</el-empty>
<!-- 保留原表格结构用于兼容(隐藏显示) -->
<el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleView" style="display: none">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="申请人" align="center" prop="applicantName" min-width="100" />
<el-table-column label="部门" align="center" prop="department" min-width="120" />
@@ -55,14 +83,10 @@
<el-table-column label="新目的地" align="center" prop="newDestination" min-width="120" />
<el-table-column label="变更类型" align="center" prop="changeType" min-width="150" />
<el-table-column label="原出国时间" align="center" min-width="200">
<template #default="scope">
{{ formatDate(scope.row.originalStartDate) }} 至 {{ formatDate(scope.row.originalEndDate) }}
</template>
<template #default="scope"> {{ formatDate(scope.row.originalStartDate) }} 至 {{ formatDate(scope.row.originalEndDate) }} </template>
</el-table-column>
<el-table-column label="新出国时间" align="center" min-width="200">
<template #default="scope">
{{ formatDate(scope.row.newStartDate) }} 至 {{ formatDate(scope.row.newEndDate) }}
</template>
<template #default="scope"> {{ formatDate(scope.row.newStartDate) }} 至 {{ formatDate(scope.row.newEndDate) }} </template>
</el-table-column>
<el-table-column label="申请时间" align="center" prop="createTime" min-width="160" />
<el-table-column label="操作" align="center" width="200" fixed="right">
@@ -71,10 +95,22 @@
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['rs:itineraryChange:query']"></el-button>
</el-tooltip>
<el-tooltip content="通过" placement="top">
<el-button link type="success" icon="Check" @click="handleApprovalPass(scope.row)" v-hasPermi="['rs:itineraryChange:approve']"></el-button>
<el-button
link
type="success"
icon="Check"
@click="handleApprovalPass(scope.row)"
v-hasPermi="['rs:itineraryChange:approve']"
></el-button>
</el-tooltip>
<el-tooltip content="拒绝" placement="top">
<el-button link type="danger" icon="Close" @click="handleApprovalReject(scope.row)" v-hasPermi="['rs:itineraryChange:approve']"></el-button>
<el-button
link
type="danger"
icon="Close"
@click="handleApprovalReject(scope.row)"
v-hasPermi="['rs:itineraryChange:approve']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
@@ -184,14 +220,14 @@
<!-- 基本信息 -->
<el-descriptions-item label="申请人">{{ viewData.applicantName || '-' }}</el-descriptions-item>
<el-descriptions-item label="部门">{{ viewData.department || '-' }}</el-descriptions-item>
<!-- 原申请信息 -->
<el-descriptions-item label="原目的地">{{ viewData.originalDestination || '-' }}</el-descriptions-item>
<el-descriptions-item label="原出境事由">{{ viewData.originalExitReason || '-' }}</el-descriptions-item>
<el-descriptions-item label="原出发日期">{{ formatDate(viewData.originalStartDate) }}</el-descriptions-item>
<el-descriptions-item label="原返回日期">{{ formatDate(viewData.originalEndDate) }}</el-descriptions-item>
<el-descriptions-item label="原出国天数" :span="2">{{ viewData.originalDuration || 0 }}天</el-descriptions-item>
<!-- 变更信息 -->
<el-descriptions-item label="变更类型" :span="2">{{ viewData.changeType || '-' }}</el-descriptions-item>
<el-descriptions-item label="新目的地" v-if="viewData.newDestination">{{ viewData.newDestination }}</el-descriptions-item>
@@ -202,7 +238,7 @@
<el-descriptions-item label="新行程安排" :span="2" v-if="viewData.newItinerary">{{ viewData.newItinerary }}</el-descriptions-item>
<el-descriptions-item label="变更原因" :span="2">{{ viewData.changeReason || '-' }}</el-descriptions-item>
<el-descriptions-item label="备注" :span="2" v-if="viewData.remark">{{ viewData.remark }}</el-descriptions-item>
<!-- 审批信息 -->
<el-descriptions-item label="变更状态">
<el-tag :type="getStatusTagType(viewData.status)">{{ viewData.statusText }}</el-tag>
@@ -230,14 +266,7 @@
</el-radio-group>
</el-form-item>
<el-form-item label="审批意见" prop="opinion">
<el-input
v-model="approvalForm.opinion"
type="textarea"
:rows="4"
placeholder="请输入审批意见"
maxlength="500"
show-word-limit
/>
<el-input v-model="approvalForm.opinion" type="textarea" :rows="4" placeholder="请输入审批意见" maxlength="500" show-word-limit />
</el-form-item>
</el-form>
@@ -248,16 +277,16 @@
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="ItineraryChangeApproval">
import { ref, reactive, toRefs, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useRouter } from 'vue-router';
import animate from '@/animate';
import { parseTime } from '@/utils/ruoyi';
import {
import {
listPendingItineraryChange,
listItineraryChange,
getItineraryChange,
@@ -269,6 +298,7 @@ import {
// ==================== 响应式数据 ====================
const router = useRouter();
const pendingQueryFormRef = ref();
const recordQueryFormRef = ref();
const approvalFormRef = ref();
@@ -276,7 +306,7 @@ const approvalFormRef = ref();
const state = reactive({
// Tab切换
activeTab: 'pending',
// 待审批列表
pendingLoading: false,
showPendingSearch: true,
@@ -288,7 +318,7 @@ const state = reactive({
applicantName: '',
destination: ''
} as ItineraryChangeQuery,
// 审批记录列表
recordLoading: false,
showRecordSearch: true,
@@ -301,7 +331,7 @@ const state = reactive({
destination: '',
status: ''
} as ItineraryChangeQuery,
// 对话框
viewDialog: {
visible: false
@@ -310,7 +340,7 @@ const state = reactive({
visible: false,
title: ''
},
// 表单数据
viewData: null as ItineraryChangeVO | null,
approvalForm: {
@@ -319,7 +349,7 @@ const state = reactive({
opinion: ''
} as ItineraryChangeApprovalRequest,
approvalLoading: false,
// 表单验证规则
approvalRules: {
approved: [{ required: true, message: '请选择审批结果', trigger: 'change' }]
@@ -358,7 +388,7 @@ onMounted(() => {
const handleTabClick = (tab: any) => {
const tabName = tab.props?.name || tab.paneName;
state.activeTab = tabName;
if (tabName === 'pending') {
getPendingList();
} else if (tabName === 'record') {
@@ -366,18 +396,17 @@ const handleTabClick = (tab: any) => {
}
};
// 查询待审批列表
// 🔴 标准化改造:简化待审批列表获取(仅用于兼容模式)
const getPendingList = async () => {
state.pendingLoading = true;
try {
const response = await listPendingItineraryChange(state.pendingQueryParams);
state.pendingList = response.rows;
state.pendingTotal = response.total;
} catch (error) {
console.error('获取待审批列表失败:', error);
} finally {
state.pendingLoading = false;
}
// 提示用户使用标准工作流页面
ElMessage.info('请使用标准工作流待办页面查看审批任务');
// 清空数据,不再调用自定义API
state.pendingList = [];
state.pendingTotal = 0;
state.pendingLoading = false;
};
// 查询审批记录列表
@@ -400,16 +429,20 @@ const getRecordList = async () => {
}
};
// 🔴 标准化改造:导航到标准工作流待办页面
const navigateToTaskWaiting = () => {
// 跳转到标准工作流待办页面
router.push('/workflow/task/taskWaiting');
};
// 搜索查询 - 待审批列表
const handlePendingQuery = () => {
state.pendingQueryParams.pageNum = 1;
getPendingList();
ElMessage.info('请使用标准工作流待办页面查看审批任务');
};
// 重置搜索 - 待审批列表
const resetPendingQuery = () => {
pendingQueryFormRef.value?.resetFields();
handlePendingQuery();
ElMessage.info('请使用标准工作流待办页面查看审批任务');
};
// 搜索查询 - 审批记录
@@ -463,7 +496,7 @@ const submitApproval = () => {
await approveItineraryChange(state.approvalForm);
ElMessage.success('审批成功');
state.approvalDialog.visible = false;
// 刷新两个列表(审批后需要同时更新待审批和审批记录列表)
await getPendingList();
await getRecordList();
@@ -488,11 +521,10 @@ const getStatusTagType = (status: string): 'primary' | 'success' | 'info' | 'war
const typeMap: Record<string, 'primary' | 'success' | 'info' | 'warning' | 'danger'> = {
'0': 'warning', // 待审核
'1': 'success', // 已通过
'2': 'danger' // 已拒绝
'2': 'danger' // 已拒绝
};
return typeMap[status] || 'info';
};
</script>
<style scoped>
@@ -518,4 +550,3 @@ const getStatusTagType = (status: string): 'primary' | 'success' | 'info' | 'war
min-height: 400px;
}
</style>
+421 -162
View File
@@ -1,9 +1,9 @@
<template>
<!-- 文件通知阅读弹窗 -->
<el-dialog
:title="noticeDialog.title"
v-model="noticeDialog.visible"
width="800px"
<el-dialog
:title="noticeDialog.title"
v-model="noticeDialog.visible"
width="800px"
top="5vh"
:close-on-click-modal="false"
:close-on-press-escape="false"
@@ -12,76 +12,55 @@
>
<div class="notice-content">
<!-- 文件头部 -->
<div class="notice-header" style="text-align: center; margin-bottom: 20px;">
<h2 style="color: #1890ff; margin: 0;">杭州第一技师学院</h2>
<h3 style="color: #666; margin: 10px 0;">关于因私出国(境)申请管理的通知</h3>
<div class="notice-header" style="text-align: center; margin-bottom: 20px">
<h2 style="color: #1890ff; margin: 0">杭州第一技师学院</h2>
<h3 style="color: #666; margin: 10px 0">关于因私出国(境)申请管理的通知</h3>
</div>
<!-- 文件内容 -->
<div class="notice-body" style="line-height: 1.8; font-size: 14px; color: #333;">
<div class="notice-body" style="line-height: 1.8; font-size: 14px; color: #333">
<p><strong>各部门、各位教职工:</strong></p>
<p>为规范我院教职工因私出国(境)申请管理,根据相关规定,现将有关事项通知如下:</p>
<p><strong>一、申请范围</strong></p>
<p>本通知适用于我院在职教职工因私事需要出国(境)的申请管理。</p>
<p><strong>二、申请条件</strong></p>
<p>1. 申请人须为我院正式在职教职工;</p>
<p>2. 无违法违纪记录;</p>
<p>3. 工作表现良好,能够按时完成本职工作。</p>
<p><strong>三、申请流程</strong></p>
<p>1. 填写《因私出国(境)申请登记报备表》;</p>
<p>2. 提交相关证明材料;</p>
<p>3. 按规定程序进行审批;</p>
<p>4. 获得批准后方可办理相关手续。</p>
<p><strong>四、注意事项</strong></p>
<p>1. 申请人应如实填写申请表,不得隐瞒或虚报;</p>
<p>2. 出国(境)期间应遵守当地法律法规;</p>
<p>3. 按时回国并及时报告相关情况;</p>
<p>4. 如有特殊情况,应及时与学院联系。</p>
<p><strong>五、其他</strong></p>
<p>本通知自发布之日起执行,如有疑问请联系人事处。</p>
<div style="text-align: right; margin-top: 30px;">
<div style="text-align: right; margin-top: 30px">
<p>杭州第一技师学院</p>
<p>{{ new Date().getFullYear() }}年{{ new Date().getMonth() + 1 }}月{{ new Date().getDate() }}日</p>
</div>
</div>
</div>
<!-- 弹窗底部操作按钮 -->
<template #footer>
<div class="dialog-footer" style="text-align: center;">
<el-button
v-if="countdownActive"
type="info"
disabled
size="large"
style="min-width: 150px;"
>
请阅读文件内容 ({{ countdown }}s)
</el-button>
<el-button
v-else-if="readCompleted"
type="primary"
@click="handleReadComplete"
size="large"
style="min-width: 150px;"
>
<div class="dialog-footer" style="text-align: center">
<el-button v-if="countdownActive" type="info" disabled size="large" style="min-width: 150px"> 请阅读文件内容 ({{ countdown }}s) </el-button>
<el-button v-else-if="readCompleted" type="primary" @click="handleReadComplete" size="large" style="min-width: 150px">
阅读完成,进入申请
</el-button>
<el-button
type="default"
@click="handleCloseNotice"
size="large"
style="margin-left: 20px;"
>
取消
</el-button>
<el-button type="default" @click="handleCloseNotice" size="large" style="margin-left: 20px"> 取消 </el-button>
</div>
</template>
</el-dialog>
@@ -90,10 +69,28 @@
<!-- 顶部按钮区域 -->
<div class="top-button-section">
<div class="left-buttons">
<el-button type="primary" :loading="loading" @click="handleSubmit" v-hasPermi="['rs:overseas:add', 'rs:overseas:edit']">
{{ isResubmit ? '修改并重新提交' : (isEdit ? '更新' : '提交') }}
</el-button>
<el-button @click="handleCancel">取消</el-button>
<!-- ========================================
🚨 标准化改造:根据模式显示不同按钮
======================================== -->
<!-- 审批模式按钮 -->
<template v-if="isApprovalMode">
<el-button type="success" :loading="approvalLoading" @click="handleApprove('1')" v-hasPermi="['rs:overseas:approve']">
<el-icon><Check /></el-icon> 通过
</el-button>
<el-button type="danger" :loading="approvalLoading" @click="handleApprove('2')" v-hasPermi="['rs:overseas:approve']">
<el-icon><Close /></el-icon> 拒绝
</el-button>
<el-button @click="handleBackToWorkflow">返回待办</el-button>
</template>
<!-- 普通编辑模式按钮 -->
<template v-else>
<el-button type="primary" :loading="loading" @click="handleSubmit" v-hasPermi="['rs:overseas:add', 'rs:overseas:edit']">
{{ isResubmit ? '修改并重新提交' : isEdit ? '更新' : '提交' }}
</el-button>
<el-button @click="handleCancel">取消</el-button>
</template>
</div>
</div>
@@ -101,17 +98,15 @@
<el-card shadow="never" class="form-card">
<template #header>
<div class="card-header">
<span class="section-title">
{{ isResubmit ? '修改并重新提交' : (isEdit ? '编辑' : '新增') }}因私出国(境)申请登记报备表
</span>
<span class="section-title"> {{ isResubmit ? '修改并重新提交' : isEdit ? '编辑' : '新增' }}因私出国(境)申请登记报备表 </span>
</div>
</template>
<el-form
ref="applicationFormRef"
:model="applicationForm"
:rules="applicationRules"
label-width="140px"
<el-form
ref="applicationFormRef"
:model="applicationForm"
:rules="applicationRules"
label-width="140px"
class="application-form"
v-loading="formLoading"
>
@@ -202,16 +197,45 @@
</el-form-item>
</el-col>
</el-row>
<!-- 第五排:户籍所在地、移动电话 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="户籍所在地" prop="registeredAddress">
<el-input v-model="applicationForm.registeredAddress" placeholder="请输入户籍所在地(填写至县市区)" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="移动电话" prop="mobilePhone">
<el-input v-model="applicationForm.mobilePhone" placeholder="请输入移动电话" />
</el-form-item>
</el-col>
</el-row>
<!-- 第六排:人员类别 -->
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="人员类别" prop="personnelCategory">
<el-select v-model="applicationForm.personnelCategory" placeholder="请选择人员类别" style="width: 100%">
<el-option label="教学人员" value="教学人员" />
<el-option label="管理人员" value="管理人员" />
<el-option label="教辅人员" value="教辅人员" />
<el-option label="工勤人员" value="工勤人员" />
<el-option label="其他人员" value="其他" />
</el-select>
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 出国信息 -->
<div class="form-section">
<div class="section-title">出国信息</div>
<!-- 第五排:前往国家、出境事由 -->
<!-- 第五排:前往国家或地区、出境事由 -->
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="前往国家" prop="destinationCountry">
<el-input v-model="applicationForm.destinationCountry" placeholder="请输入前往国家" />
<el-form-item label="前往国家或地区" prop="destinationCountry">
<el-input v-model="applicationForm.destinationCountry" placeholder="请输入前往国家或地区" />
</el-form-item>
</el-col>
<el-col :span="16">
@@ -227,10 +251,21 @@
<!-- 探亲时显示的额外字段 -->
<el-row :gutter="20" v-if="applicationForm.exitReason === '探亲'">
<el-col :span="24">
<el-form-item label="探望对象关系" prop="visitRelationship">
<el-input
v-model="applicationForm.visitRelationship"
placeholder="请输入探望对象姓名及与申请者关系"
<el-form-item label="探望对象姓名及与申请者关系" prop="visitRelationship">
<el-input v-model="applicationForm.visitRelationship" placeholder="请输入探望对象姓名及与申请者关系" />
</el-form-item>
</el-col>
</el-row>
<!-- 备注 -->
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="applicationForm.remarks"
type="textarea"
:rows="2"
placeholder="请输入备注信息(可选)"
/>
</el-form-item>
</el-col>
@@ -240,10 +275,10 @@
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="行程安排" prop="itinerary">
<el-input
v-model="applicationForm.itinerary"
type="textarea"
:rows="3"
<el-input
v-model="applicationForm.itinerary"
type="textarea"
:rows="3"
placeholder="请输入详细的行程安排(如:访问城市、主要活动、住宿安排等)"
/>
</el-form-item>
@@ -253,7 +288,7 @@
<!-- 出国(境)时间 -->
<el-row :gutter="10">
<el-col :span="2">
<el-form-item label="出国(境)时间" label-width="110px" style="font-weight: 600; white-space: nowrap;">
<el-form-item label="出国(境)时间" label-width="110px" style="font-weight: 600; white-space: nowrap">
<span></span>
</el-form-item>
</el-col>
@@ -263,7 +298,7 @@
v-model="applicationForm.startDate"
type="date"
placeholder="请选择出发日期"
style="width: 100%; min-width: 180px;"
style="width: 100%; min-width: 180px"
format="YYYY年MM月DD日"
value-format="YYYY-MM-DD"
/>
@@ -275,7 +310,7 @@
v-model="applicationForm.endDate"
type="date"
placeholder="请选择返回日期"
style="width: 100%; min-width: 180px;"
style="width: 100%; min-width: 180px"
format="YYYY年MM月DD日"
value-format="YYYY-MM-DD"
/>
@@ -283,7 +318,7 @@
</el-col>
<el-col :span="5" :offset="1">
<el-form-item label="出国天数" prop="duration">
<el-input v-model="applicationForm.duration" placeholder="天数" disabled style="min-width: 120px;">
<el-input v-model="applicationForm.duration" placeholder="天数" disabled style="min-width: 120px">
<template #append>天</template>
</el-input>
</el-form-item>
@@ -291,30 +326,22 @@
</el-row>
<!-- 党籍保留附件上传区域(仅当中共党员且出国天数>30天时显示) -->
<div
<div
v-if="showPartyRetentionUpload"
class="party-retention-upload-section"
style="margin: 10px 0; padding: 10px; border: 1px dashed #ff4757; border-radius: 4px; background: #fff5f5;">
<div style="font-size: 14px; color: #ff4757; margin-bottom: 8px; font-weight: bold;">
class="party-retention-upload-section"
style="margin: 10px 0; padding: 10px; border: 1px dashed #ff4757; border-radius: 4px; background: #fff5f5"
>
<div style="font-size: 14px; color: #ff4757; margin-bottom: 8px; font-weight: bold">
<el-icon><Warning /></el-icon>
党籍保留附件上传(必填)
</div>
<div style="font-size: 12px; color: #606266; margin-bottom: 8px;">
<div style="font-size: 12px; color: #606266; margin-bottom: 8px">
您是中共党员且出国天数超过30天,需要先提交党籍保留流程,审批通过后下载并上传盖章附件
</div>
<!-- 下载PDF空白模板按钮 -->
<div style="margin-bottom: 10px;">
<el-button
type="primary"
size="small"
icon="Download"
@click="handleDownloadPartyRetention"
>
下载党籍保留申请PDF模板
</el-button>
<span style="margin-left: 10px; font-size: 12px; color: #909399;">
* 请先下载空白模板并手工填写,然后上传盖章后的文件
</span>
<div style="margin-bottom: 10px">
<el-button type="primary" size="small" icon="Download" @click="handleDownloadPartyRetention"> 下载党籍保留申请PDF模板 </el-button>
<span style="margin-left: 10px; font-size: 12px; color: #909399"> * 请先下载空白模板并手工填写,然后上传盖章后的文件 </span>
</div>
<div class="attachment-upload-container">
<el-upload
@@ -332,13 +359,9 @@
list-type="text"
class="upload-demo"
>
<el-button size="small" type="primary" icon="Upload">
上传党籍保留附件
</el-button>
<el-button size="small" type="primary" icon="Upload"> 上传党籍保留附件 </el-button>
<template #tip>
<div class="el-upload__tip" style="font-size: 12px; color: #909399;">
支持 PDF、图片、Word 格式,文件大小不超过 10MB
</div>
<div class="el-upload__tip" style="font-size: 12px; color: #909399">支持 PDF、图片、Word 格式,文件大小不超过 10MB</div>
</template>
</el-upload>
</div>
@@ -354,19 +377,17 @@
</el-form-item>
</el-col>
</el-row>
</div>
<!-- 备注 -->
<div class="form-section">
<!-- 联系方式 -->
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注" prop="remarks">
<el-input
v-model="applicationForm.remarks"
type="textarea"
:rows="1"
placeholder="请输入备注信息"
/>
<el-col :span="12">
<el-form-item label="联系电话" prop="mobilePhone">
<el-input v-model="applicationForm.mobilePhone" placeholder="请输入联系电话" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="应急联系人及联系电话" prop="emergencyContact">
<el-input v-model="applicationForm.emergencyContact" placeholder="请输入应急联系人及联系电话" />
</el-form-item>
</el-col>
</el-row>
@@ -381,9 +402,17 @@ import { ref, reactive, computed, onMounted, getCurrentInstance, watch, nextTick
import { useRoute, useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import type { FormInstance, FormRules } from 'element-plus';
import { Warning, Download, Upload } from '@element-plus/icons-vue';
import { Warning, Download, Upload, Check, Close } from '@element-plus/icons-vue';
import { getToken } from '@/utils/auth';
import request from '@/utils/request';
import {
addOverseasApplication,
updateOverseasApplication,
getOverseasApplication,
approveOverseasApplication,
type OverseasApplicationForm,
type ApprovalRequest
} from '@/api/rs/overseas';
// 获取当前实例和路由
const { proxy } = getCurrentInstance();
@@ -396,6 +425,16 @@ const formLoading = ref(false);
const applicationFormRef = ref<FormInstance>();
const partyRetentionUploadRef = ref();
// ========================================
// 🚨 标准化改造:审批相关状态
// ========================================
const approvalLoading = ref(false);
const approvalDialogVisible = ref(false);
const approvalForm = reactive({
approvalOpinion: '',
approvalStatus: '1'
});
// 党籍保留附件相关
const partyRetentionFileList = ref([]);
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + '/rs/overseas/application/attachment/upload');
@@ -430,12 +469,27 @@ const uploadHeaders = computed(() => {
};
});
// ========================================
// 🚨 标准化改造:支持工作流审批模式
// ========================================
// 判断是否为审批模式(通过工作流跳转)
const isApprovalMode = computed(() => !!route.query.taskId);
// 判断是否为编辑模式
const isEdit = computed(() => route.query.type === 'edit' || route.query.type === 'resubmit' || !!route.query.id);
// 判断是否为重新提交模式
const isResubmit = computed(() => route.query.type === 'resubmit');
// 获取工作流任务ID
const taskId = computed(() => route.query.taskId as string);
// 获取申请ID(从路由参数)
const applicationId = computed(() => {
return route.query.applicationId || route.query.id;
});
// 申请表单数据
const applicationForm = reactive({
applicationId: '',
@@ -448,6 +502,9 @@ const applicationForm = reactive({
position: '',
documentName: '',
documentNumber: '',
registeredAddress: '', // ✅ 新增:户籍所在地
mobilePhone: '', // ✅ 新增:移动电话
personnelCategory: '', // ✅ 新增:人员类别
destinationCountry: '',
exitReason: '',
visitRelationship: '',
@@ -471,6 +528,13 @@ const applicationRules: FormRules = {
position: [{ required: true, message: '请输入职务', trigger: 'blur' }],
documentName: [{ required: true, message: '请输入申领证件名称', trigger: 'blur' }],
documentNumber: [{ required: true, message: '请输入证件号码', trigger: 'blur' }],
// ✅ 新增必填字段验证
registeredAddress: [{ required: true, message: '请输入户籍所在地', trigger: 'blur' }],
mobilePhone: [
{ required: true, message: '请输入移动电话', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '移动电话格式不正确', trigger: 'blur' }
],
personnelCategory: [{ required: true, message: '请选择人员类别', trigger: 'change' }],
destinationCountry: [{ required: true, message: '请输入前往国家', trigger: 'blur' }],
exitReason: [{ required: true, message: '请选择出境事由', trigger: 'change' }],
itinerary: [{ required: true, message: '请输入行程安排', trigger: 'blur' }],
@@ -497,19 +561,19 @@ watch([() => applicationForm.startDate, () => applicationForm.endDate], () => {
// 计算属性:是否显示党籍保留上传组件
const showPartyRetentionUpload = computed(() => {
// 首先检查是否为中共党员
const isPartyMember = applicationForm.politicalStatus === '1' || applicationForm.politicalStatus === '中共党员';
const isPartyMember = applicationForm.politicalStatus === '1' || applicationForm.politicalStatus === '中共党员';
// 如果不是党员,直接返回false
if (!isPartyMember) {
return false;
}
// 检查出国天数
const duration = applicationForm.duration;
if (!duration) {
return false;
}
// 出国天数大于30天才显示
return Number(duration) > 30;
});
@@ -529,7 +593,7 @@ watch(
const handleDownloadPartyRetention = async () => {
try {
ElMessage.info('正在生成PDF,请稍候...');
// 使用request工具发送请求,自动包含认证头
const response = await request({
url: '/rs/overseas/record/downloadPartyRetention',
@@ -538,17 +602,16 @@ const handleDownloadPartyRetention = async () => {
});
// 创建blob并触发下载(response已经是blob对象)
const blob = response instanceof Blob
? new Blob([response], { type: 'application/pdf' })
: new Blob([response.data], { type: 'application/pdf' });
const blob =
response instanceof Blob ? new Blob([response], { type: 'application/pdf' }) : new Blob([response.data], { type: 'application/pdf' });
console.log('📥 生成的Blob大小:', blob.size, 'bytes');
console.log('📥 Blob类型:', blob.type);
if (blob.size === 0) {
throw new Error('下载的文件为空');
}
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
@@ -569,9 +632,14 @@ const handleDownloadPartyRetention = async () => {
// 党籍保留附件上传前检查
const beforePartyRetentionUpload = async (file: any) => {
// 检查文件格式
const isValidType = ['application/pdf', 'image/jpeg', 'image/jpg', 'image/png',
'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']
.includes(file.type);
const isValidType = [
'application/pdf',
'image/jpeg',
'image/jpg',
'image/png',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
].includes(file.type);
const isLt10M = file.size / 1024 / 1024 < 10;
if (!isValidType) {
@@ -637,7 +705,7 @@ const handlePartyRetentionRemove = (file: any) => {
const startCountdown = () => {
countdownActive.value = true;
readCompleted.value = false;
const timer = setInterval(() => {
if (countdown.value > 0) {
countdown.value--;
@@ -672,85 +740,276 @@ const handleCloseNotice = () => {
// 提交表单
const handleSubmit = async () => {
if (!applicationFormRef.value) return;
try {
await applicationFormRef.value.validate();
// 检查党员是否需要上传党籍保留附件
if (showPartyRetentionUpload.value && !applicationForm.partyRetentionAttachment) {
ElMessage.error('请上传党籍保留附件');
return;
}
loading.value = true;
// 这里调用保存API
// 由于我只能修改rs模块内容,暂时模拟提交成功
await new Promise(resolve => setTimeout(resolve, 1000));
const successMessage = isResubmit.value
? '修改并重新提交成功!您的申请已重新进入审批流程。'
: (isEdit.value ? '更新成功' : '申请提交成功!您的申请已进入审批流程,请耐心等待审批结果。');
// 准备提交数据 - 修复字段映射关系
const submitData: OverseasApplicationForm = {
...applicationForm,
// ✅ 按照纸质版表格字段直接映射,不做转换
// 保持前端字段名与数据库字段名一致
destinationCountry: applicationForm.destinationCountry || '', // 前往国家或地区
exitReason: applicationForm.exitReason || '', // 出境事由
itinerary: applicationForm.itinerary || '', // 行程安排
// 以下字段用于兼容后端现有逻辑
purpose: applicationForm.exitReason || '', // 兼容字段
purposeDetail: applicationForm.itinerary || '', // 兼容字段
destination: applicationForm.destinationCountry || '', // 兼容字段
// ✅ 使用用户实际填写的字段值,不再使用默认值
registeredAddress: applicationForm.registeredAddress || '',
mobilePhone: applicationForm.mobilePhone || '',
personnelCategory: applicationForm.personnelCategory || '',
emergencyContact: applicationForm.emergencyContact || '', // 应急联系人及联系电话
remarks: applicationForm.remarks || '', // 备注
// 保留的默认值字段
budget: 0, // 如果没有预算字段,设置默认值
contactPhone: applicationForm.mobilePhone || '暂无', // 使用移动电话作为联系电话
duration: applicationForm.duration ? Number(applicationForm.duration) : 0
};
// 新增模式:删除系统自动设置的字段,避免验证错误
if (!isEdit.value) {
delete submitData.applicantId;
// ✅ 保留用户填写的姓名字段,不要删除
// delete submitData.applicantName;
// ✅ 保留部门字段,后端需要此字段
// delete submitData.department;
delete submitData.tenantId;
delete submitData.applyTime;
}
// 保留办公电话字段的过滤逻辑(可选字段)
if (!submitData.officePhone) delete submitData.officePhone;
let result;
if (isEdit.value && applicationForm.applicationId) {
// 更新模式
result = await updateOverseasApplication(submitData);
} else {
// 新增模式
result = await addOverseasApplication(submitData);
// 保存返回的applicationId
if (result.data?.applicationId) {
applicationForm.applicationId = result.data.applicationId;
}
}
const successMessage = isResubmit.value
? '修改并重新提交成功!您的申请已重新进入审批流程。'
: isEdit.value
? '更新成功'
: '申请提交成功!您的申请已进入审批流程,请耐心等待审批结果。';
ElMessage.success(successMessage);
// 返回上一页
handleCancel();
} catch (error) {
console.error('表单验证失败:', error);
handleGoBack();
} catch (error: any) {
console.error('提交失败:', error);
ElMessage.error(error?.message || '提交失败,请稍后重试');
} finally {
loading.value = false;
}
};
// 取消操作
// 保存申请(用于获取applicationId)
const handleSave = async () => {
if (!applicationFormRef.value) return;
await applicationFormRef.value.validate();
// 如果已有ID,无需重复保存
if (applicationForm.applicationId) {
return;
}
// 准备保存数据 - 修复字段映射关系
const saveData: OverseasApplicationForm = {
...applicationForm,
// ✅ 按照纸质版表格字段直接映射,不做转换
// 保持前端字段名与数据库字段名一致
destinationCountry: applicationForm.destinationCountry || '', // 前往国家或地区
exitReason: applicationForm.exitReason || '', // 出境事由
itinerary: applicationForm.itinerary || '', // 行程安排
// 以下字段用于兼容后端现有逻辑
purpose: applicationForm.exitReason || '', // 兼容字段
purposeDetail: applicationForm.itinerary || '', // 兼容字段
destination: applicationForm.destinationCountry || '', // 兼容字段
// ✅ 使用用户实际填写的字段值,不再使用默认值
registeredAddress: applicationForm.registeredAddress || '',
mobilePhone: applicationForm.mobilePhone || '',
personnelCategory: applicationForm.personnelCategory || '',
emergencyContact: applicationForm.emergencyContact || '', // 应急联系人及联系电话
remarks: applicationForm.remarks || '', // 备注
// 保留的默认值字段
budget: 0, // 如果没有预算字段,设置默认值
contactPhone: applicationForm.mobilePhone || '暂无', // 使用移动电话作为联系电话
duration: applicationForm.duration ? Number(applicationForm.duration) : 0
};
// 删除系统自动设置的字段,避免验证错误
delete saveData.applicantId;
// ✅ 保留用户填写的姓名字段,不要删除
// delete saveData.applicantName;
delete saveData.department;
delete saveData.tenantId;
delete saveData.applyTime;
// 保留办公电话字段的过滤逻辑(可选字段)
if (!saveData.officePhone) delete saveData.officePhone;
try {
const result = await addOverseasApplication(saveData);
if (result.data?.applicationId) {
applicationForm.applicationId = result.data.applicationId;
ElMessage.success('申请信息保存成功');
}
} catch (error) {
console.error('保存失败:', error);
throw new Error('保存申请信息失败');
}
};
// 直接返回上一页(不弹确认框)
const handleGoBack = () => {
// 关闭当前页面,返回上一页
proxy.$tab.closePage(route);
router.go(-1);
};
// 取消操作(弹出确认框)
const handleCancel = () => {
ElMessageBox.confirm('是否确认取消操作?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 关闭当前页面,返回上一页
proxy.$tab.closePage(route);
router.go(-1);
}).catch(() => {
// 用户取消
});
})
.then(() => {
handleGoBack();
})
.catch(() => {
// 用户取消
});
};
// 初始化数据
const initData = async () => {
if (isEdit.value && route.query.id) {
// 🔴 标准化改造:支持审批模式和编辑模式
const targetId = applicationId.value;
if ((isEdit.value && route.query.id) || (isApprovalMode.value && targetId)) {
formLoading.value = true;
try {
// 这里应该根据ID获取数据
// 由于我只能修改rs模块内容,暂时模拟数据加载
await new Promise(resolve => setTimeout(resolve, 500));
// 模拟填充编辑数据
Object.assign(applicationForm, {
applicationId: route.query.id,
applicantName: '张三',
gender: '男',
// ... 其他字段
});
// 根据ID获取申请数据(支持审批模式和编辑模式)
const result = await getOverseasApplication(targetId as string);
if (result.data) {
// 映射数据到表单 - 修复字段映射关系
Object.assign(applicationForm, {
applicationId: result.data.applicationId,
applicantName: result.data.applicantName || '',
gender: result.data.gender || '',
birthDate: result.data.birthDate || '',
politicalStatus: result.data.politicalStatus || '',
department: result.data.department || '',
professionalTitle: result.data.professionalTitle || '',
position: result.data.position || '',
documentName: result.data.documentName || '',
documentNumber: result.data.documentNumber || '',
// ✅ 新增字段的映射
registeredAddress: result.data.registeredAddress || '',
mobilePhone: result.data.mobilePhone || '',
personnelCategory: result.data.personnelCategory || '',
// ✅ 修复字段映射关系
destinationCountry: result.data.destination || '', // 后端destination -> 前端destinationCountry
exitReason: result.data.purpose || '', // 后端purpose -> 前端exitReason
itinerary: result.data.purposeDetail || result.data.itinerary || '', // 后端purposeDetail -> 前端itinerary
visitRelationship: result.data.visitRelationship || '',
startDate: result.data.startDate || '',
endDate: result.data.endDate || '',
duration: result.data.duration || '',
remarks: result.data.applicantRemark || result.data.remarks || '',
partyRetentionAttachment: result.data.partyRetentionFilePath || '',
partyRetentionAttachmentId: result.data.partyRetentionFileOssId || ''
});
}
} catch (error) {
console.error('获取数据失败:', error);
ElMessage.error('获取数据失败');
ElMessage.error('获取申请数据失败');
} finally {
formLoading.value = false;
}
}
};
// ========================================
// 🚨 标准化改造:审批相关处理函数
// ========================================
// 处理审批操作
const handleApprove = async (approvalStatus: string) => {
try {
const title = approvalStatus === '1' ? '审批通过' : '审批拒绝';
const placeholder = approvalStatus === '1' ? '请填写审批意见(可选)' : '请填写拒绝理由';
const { value: opinion } = await ElMessageBox.prompt(
`确定要${title}此申请吗?`,
title,
{
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPlaceholder: placeholder,
inputType: 'textarea',
inputValidator: approvalStatus === '2' ? (value) => {
return value && value.trim() !== '' || '拒绝时必须填写理由';
} : null
}
);
approvalLoading.value = true;
const approvalRequest: ApprovalRequest = {
applicationId: applicationId.value as string,
approvalStatus,
approvalOpinion: opinion || '',
taskId: taskId.value
};
await approveOverseasApplication(approvalRequest);
ElMessage.success(`${title}成功!`);
// 返回到待办任务页面
handleBackToWorkflow();
} catch (error: any) {
if (error !== 'cancel') {
console.error('审批失败:', error);
ElMessage.error(error?.message || '审批失败,请稍后重试');
}
} finally {
approvalLoading.value = false;
}
};
// 返回到工作流待办页面
const handleBackToWorkflow = () => {
router.push('/workflow/task/taskWaiting');
};
// 组件挂载时初始化
onMounted(() => {
nextTick(() => {
initData();
// 新增模式下显示文件阅读弹窗
if (route.query.type === 'add') {
// 新增模式下显示文件阅读弹窗(审批模式下不显示)
if (route.query.type === 'add' && !isApprovalMode.value) {
noticeDialog.visible = true;
startCountdown();
}
@@ -816,7 +1075,7 @@ onMounted(() => {
.el-col-8 {
width: 100% !important;
}
.el-col-12 {
width: 100% !important;
}
+17 -8
View File
@@ -463,14 +463,23 @@ function handleTabClick(tab) {
/** 查询出国审批列表 */
function getList() {
loading.value = true;
console.log('临时清空出国审批列表内容');
// 临时清空列表内容
approvalList.value = [];
total.value = 0;
loading.value = false;
console.log('出国审批列表已清空');
const params = {
...queryParams.value,
pageNum: queryParams.value.pageNum,
pageSize: queryParams.value.pageSize
};
listOverseasApproval(params)
.then((response) => {
approvalList.value = response.rows;
total.value = response.total;
loading.value = false;
console.log('出国审批列表查询成功,记录数:', total.value);
})
.catch((error) => {
console.error('查询出国审批列表失败:', error);
loading.value = false;
});
}
/** 查询审批记录列表 */