提交代码

This commit is contained in:
zlwu committed 2025-11-28 20:01:32 +08:00
1 parent 4e34c7eb36
commit 375a2c8df7
54 files changed
+4631 -4352

No files matched your search

+13 -1
View File
@@ -70,6 +70,7 @@ export interface OverseasRegistrationForm {
officePhone?: string; // 办公电话
mobilePhone?: string; // 移动电话
remarks?: string; // 备注
status?: string; // 登记状态:0-草稿,1-待审核,2-已通过,3-已拒绝
}
// 登记历史查询参数类型
@@ -142,6 +143,17 @@ export function getOverseasRegistrationList(query: OverseasRegistrationQuery) {
});
}
/**
* 查询我的出国登记列表(只查询当前登录人创建的记录)
*/
export function getMyOverseasRegistrationList(query: OverseasRegistrationQuery) {
return request({
url: '/rs/overseas/registration/my/list',
method: 'get',
params: query
});
}
/**
* 查询出国登记详细
*/
@@ -433,4 +445,4 @@ export function batchRejectOverseasRegistration(registrationIds: Array<number |
...data
}
});
}
}
+14 -3
View File
@@ -60,7 +60,7 @@ export interface OverseasApplicationVO {
partyRetentionFileName?: string; // 党籍保留附件文件名
partyRetentionUploadTime?: string; // 党籍保留附件上传时间
partyRetentionUploadStatus?: string; // 党籍保留附件上传状态
partyRetentionFileOssId?: number; // 党籍保留附件OSS ID
partyRetentionFileOssId?: string; // 党籍保留附件OSS ID(支持多个ID用逗号分隔)
purpose: string;
purposeDetail: string;
destination: string;
@@ -137,6 +137,7 @@ export interface OverseasApplicationForm {
partyRetentionFileName?: string; // 党籍保留附件文件名
partyRetentionUploadTime?: string; // 党籍保留附件上传时间
partyRetentionUploadStatus?: string; // 党籍保留附件上传状态:0-未上传,1-已上传
partyRetentionFileOssId?: string; // 党籍保留附件OSS ID(支持多个ID用逗号分隔)
purpose: string;
purposeDetail: string;
destination: string;
@@ -341,6 +342,15 @@ export function listPendingOverseasApplication() {
});
}
// 查询当前用户的待审批申请分页列表(根据角色过滤,支持分页和搜索)
export function listPendingOverseasApplicationPage(query: OverseasApplicationQuery) {
return request({
url: '/rs/overseas/application/pending/page',
method: 'get',
params: query
});
}
// ========================================
// 🚨 标准化改造:删除自定义待审批查询API
//
@@ -805,6 +815,7 @@ export interface ItineraryChangeQuery {
applicantName?: string;
destination?: string;
status?: string;
applicantId?: number;
params?: Record<string, any>;
}
@@ -842,8 +853,8 @@ export interface ItineraryChangeVO {
status: string;
statusText: string;
approvalOpinion?: string;
approvalBy?: number;
approvalByName?: string;
approverId?: number;
approverName?: string;
approvalTime?: string;
createTime: string;
updateTime?: string;
+1 -1
View File
@@ -34,7 +34,7 @@ router.beforeEach(async (to, from, next) => {
const [err] = await tos(useUserStore().getInfo());
if (err) {
await useUserStore().logout();
ElMessage.error(err);
// ElMessage.error(err.message || '获取用户信息失败');
next({ path: '/' });
} else {
isRelogin.show = false;
+8 -2
View File
@@ -204,8 +204,14 @@ service.interceptors.response.use(
err.response = res;
return Promise.reject(err);
} else if (code !== HttpStatus.SUCCESS) {
ElNotification.error({ title: msg });
const err = new Error(msg) as any;
// 确保错误消息不为空,避免只显示图标没有文字
const errorMsg = msg || errorCode['default'] || '操作失败,请稍后重试';
ElNotification.error({
title: '错误提示',
message: errorMsg,
duration: 5000
});
const err = new Error(errorMsg) as any;
err.response = res;
return Promise.reject(err);
} else {
+22 -6
View File
@@ -192,13 +192,29 @@ const getLoginData = () => {
* 获取租户列表
*/
const initTenantList = async () => {
const { data } = await getTenantList(false);
tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
if (tenantEnabled.value) {
tenantList.value = data.voList;
if (tenantList.value != null && tenantList.value.length !== 0) {
loginForm.value.tenantId = tenantList.value[0].tenantId;
try {
const [err, res] = await to(getTenantList(false));
if (err) {
// 静默处理租户列表获取失败,不影响登录功能
console.warn('获取租户列表失败:', err);
// 设置默认值,确保登录功能正常
tenantEnabled.value = true;
return;
}
if (res && res.data) {
const { data } = res;
tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
if (tenantEnabled.value) {
tenantList.value = data.voList;
if (tenantList.value != null && tenantList.value.length !== 0) {
loginForm.value.tenantId = tenantList.value[0].tenantId;
}
}
}
} catch (error) {
// 捕获其他可能的错误,静默处理
console.warn('初始化租户列表时发生错误:', error);
tenantEnabled.value = true;
}
};
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,30 +1,6 @@
<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">
<!-- 待审批 -->
@@ -69,13 +45,8 @@
</el-row>
</template>
<!-- 🔴 标准化改造:显示迁移提示 -->
<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 v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleView">
<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" />
@@ -190,7 +161,7 @@
</template>
</el-table-column>
<el-table-column label="审批时间" align="center" prop="approvalTime" min-width="160" />
<el-table-column label="审批人" align="center" prop="approvalByName" min-width="100" />
<el-table-column label="审批人" align="center" prop="approverName" min-width="100" show-overflow-tooltip />
<el-table-column label="操作" align="center" width="80" fixed="right">
<template #default="scope">
<el-tooltip content="查看详情" placement="top">
@@ -243,7 +214,7 @@
<el-tag :type="getStatusTagType(viewData.status)">{{ viewData.statusText }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ viewData.createTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="审批人" v-if="viewData.approvalByName">{{ viewData.approvalByName }}</el-descriptions-item>
<el-descriptions-item label="审批人" v-if="viewData.approverName">{{ viewData.approverName }}</el-descriptions-item>
<el-descriptions-item label="审批时间" v-if="viewData.approvalTime">{{ viewData.approvalTime }}</el-descriptions-item>
<el-descriptions-item label="审批意见" :span="2" v-if="viewData.approvalOpinion">{{ viewData.approvalOpinion }}</el-descriptions-item>
</el-descriptions>
@@ -267,16 +238,10 @@
<el-form-item label="审批意见" prop="opinion">
<el-input v-model="approvalForm.opinion" type="textarea" :rows="4" placeholder="请输入审批意见" maxlength="500" show-word-limit />
</el-form-item>
<!-- 手写签字 -->
<el-form-item label="审批签字">
<SignaturePad
v-model="approvalForm.signature"
:width="450"
:height="120"
:auto-save="true"
:show-save-button="false"
/>
<SignaturePad v-model="approvalForm.signature" :width="450" :height="120" :auto-save="true" :show-save-button="false" />
<div class="signature-tip">请在上方签字板中手写签名</div>
</el-form-item>
</el-form>
@@ -413,17 +378,24 @@ const handleTabClick = (tab: any) => {
}
};
// 🔴 标准化改造:简化待审批列表获取(仅用于兼容模式)
// 获取待审批变更列表(status=0)
const getPendingList = async () => {
state.pendingLoading = true;
// 提示用户使用标准工作流页面
ElMessage.info('请使用标准工作流待办页面查看审批任务');
// 清空数据,不再调用自定义API
state.pendingList = [];
state.pendingTotal = 0;
state.pendingLoading = false;
try {
// 查询待审核状态的变更申请(status='0')
const params = { ...state.pendingQueryParams };
params.status = '0'; // 只查询待审核的
const response = await listPendingItineraryChange(params);
state.pendingList = response.rows || [];
state.pendingTotal = response.total || 0;
} catch (error) {
console.error('获取待审批列表失败:', error);
ElMessage.error('获取待审批列表失败');
state.pendingList = [];
state.pendingTotal = 0;
} finally {
state.pendingLoading = false;
}
};
// 查询审批记录列表
@@ -446,20 +418,22 @@ const getRecordList = async () => {
}
};
// 🔴 标准化改造:导航到标准工作流待办页面
const navigateToTaskWaiting = () => {
// 跳转到标准工作流待办页面
router.push('/workflow/task/taskWaiting');
};
// 搜索查询 - 待审批列表
const handlePendingQuery = () => {
ElMessage.info('请使用标准工作流待办页面查看审批任务');
state.pendingQueryParams.pageNum = 1;
getPendingList();
};
// 重置搜索 - 待审批列表
const resetPendingQuery = () => {
ElMessage.info('请使用标准工作流待办页面查看审批任务');
pendingQueryFormRef.value?.resetFields();
Object.assign(state.pendingQueryParams, {
pageNum: 1,
pageSize: 10,
applicantName: '',
destination: ''
});
getPendingList();
};
// 搜索查询 - 审批记录
@@ -574,4 +548,7 @@ const getStatusTagType = (status: string): 'primary' | 'success' | 'info' | 'war
color: #909399;
text-align: center;
}
:global(.app-container) {
padding: 0 !important;
}
</style>
+239 -182
View File
@@ -3,7 +3,6 @@
<el-card class="box-card">
<!-- Tab切换:变更申请 / 变更记录 -->
<el-tabs v-model="activeTab" type="border-card" @tab-click="handleTabClick">
<!-- Tab 1: 变更申请(待审核) -->
<el-tab-pane label="变更申请(待审核)" name="all">
<div class="tab-content">
@@ -23,15 +22,6 @@
<el-input v-model="queryParams.destination" placeholder="请输入目的地国家" clearable />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="变更状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择变更状态" clearable style="width: 100%">
<el-option label="待审核" value="0" />
<el-option label="已通过" value="1" />
<el-option label="已拒绝" value="2" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
@@ -52,9 +42,7 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['rs:itineraryChange:add']">
新增变更申请
</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['rs:itineraryChange:add']"> 新增变更申请 </el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -62,45 +50,65 @@
<!-- 变更行程列表 -->
<el-table v-loading="loading" border :data="changeList" @row-dblclick="handleView">
<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="originalDestination" min-width="120" />
<el-table-column label="新目的地" align="center" prop="newDestination" min-width="120" />
<el-table-column label="原出国时间" align="center" min-width="200">
<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>
</el-table-column>
<el-table-column label="变更状态" align="center" prop="statusText" min-width="100">
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.status)">{{ scope.row.statusText }}</el-tag>
</template>
</el-table-column>
<el-table-column label="申请时间" align="center" prop="createTime" min-width="160" />
<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="originalDestination" min-width="120" />
<el-table-column label="新目的地" align="center" prop="newDestination" min-width="120" />
<el-table-column label="原出国时间" align="center" min-width="200">
<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>
</el-table-column>
<el-table-column label="变更状态" align="center" prop="statusText" min-width="100">
<template #default="scope">
<el-tag :type="getStatusTagType(scope.row.status)">{{ scope.row.statusText }}</el-tag>
</template>
</el-table-column>
<el-table-column label="申请时间" align="center" prop="createTime" min-width="160" />
<el-table-column label="操作" align="center" width="250" fixed="right">
<template #default="scope">
<el-tooltip content="查看详情" placement="top">
<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="info" icon="List" @click="handleViewHistory(scope.row)" v-hasPermi="['rs:itineraryChange:query']"></el-button>
<el-button
link
type="info"
icon="List"
@click="handleViewHistory(scope.row)"
v-hasPermi="['rs:itineraryChange:query']"
></el-button>
</el-tooltip>
<!-- 待审核状态:显示撤回按钮 -->
<el-tooltip content="撤回申请" placement="top" v-if="scope.row.status === '0'">
<el-button link type="warning" icon="RefreshLeft" @click="handleCancel(scope.row)" v-hasPermi="['rs:itineraryChange:cancel']"></el-button>
<el-button
link
type="warning"
icon="RefreshLeft"
@click="handleCancel(scope.row)"
v-hasPermi="['rs:itineraryChange:cancel']"
></el-button>
</el-tooltip>
<!-- 已拒绝状态:显示重新提交按钮 -->
<el-tooltip content="重新提交" placement="top" v-if="scope.row.status === '2'">
<el-button link type="success" icon="RefreshRight" @click="handleResubmit(scope.row)" v-hasPermi="['rs:itineraryChange:edit']"></el-button>
<!-- 已拒绝或已撤回状态:显示重新提交按钮 -->
<el-tooltip content="重新提交" placement="top" v-if="scope.row.status === '2' || scope.row.status === '3'">
<el-button
link
type="success"
icon="RefreshRight"
@click="handleResubmit(scope.row)"
v-hasPermi="['rs:itineraryChange:edit']"
></el-button>
</el-tooltip>
<!-- 删除按钮 -->
<el-tooltip content="删除" placement="top" v-if="scope.row.status !== '1'">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['rs:itineraryChange:remove']"></el-button>
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['rs:itineraryChange:remove']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
@@ -131,15 +139,6 @@
<el-input v-model="myQueryParams.destination" placeholder="请输入目的地国家" clearable />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="变更状态" prop="status">
<el-select v-model="myQueryParams.status" placeholder="请选择变更状态" clearable style="width: 100%">
<el-option label="待审核" value="0" />
<el-option label="已通过" value="1" />
<el-option label="已拒绝" value="2" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row>
@@ -169,14 +168,10 @@
<el-table-column label="原目的地" align="center" prop="originalDestination" min-width="120" />
<el-table-column label="新目的地" align="center" prop="newDestination" min-width="120" />
<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="statusText" min-width="100">
<template #default="scope">
@@ -190,19 +185,43 @@
<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="info" icon="List" @click="handleViewHistory(scope.row)" v-hasPermi="['rs:itineraryChange:query']"></el-button>
<el-button
link
type="info"
icon="List"
@click="handleViewHistory(scope.row)"
v-hasPermi="['rs:itineraryChange:query']"
></el-button>
</el-tooltip>
<!-- 待审核状态:显示撤回按钮 -->
<el-tooltip content="撤回申请" placement="top" v-if="scope.row.status === '0'">
<el-button link type="warning" icon="RefreshLeft" @click="handleCancel(scope.row)" v-hasPermi="['rs:itineraryChange:cancel']"></el-button>
<el-button
link
type="warning"
icon="RefreshLeft"
@click="handleCancel(scope.row)"
v-hasPermi="['rs:itineraryChange:cancel']"
></el-button>
</el-tooltip>
<!-- 已拒绝状态:显示重新提交按钮 -->
<el-tooltip content="重新提交" placement="top" v-if="scope.row.status === '2'">
<el-button link type="success" icon="RefreshRight" @click="handleResubmit(scope.row)" v-hasPermi="['rs:itineraryChange:edit']"></el-button>
<!-- 已拒绝或已撤回状态:显示重新提交按钮 -->
<el-tooltip content="重新提交" placement="top" v-if="scope.row.status === '2' || scope.row.status === '3'">
<el-button
link
type="success"
icon="RefreshRight"
@click="handleResubmit(scope.row)"
v-hasPermi="['rs:itineraryChange:edit']"
></el-button>
</el-tooltip>
<!-- 删除按钮 -->
<el-tooltip content="删除" placement="top" v-if="scope.row.status !== '1'">
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['rs:itineraryChange:remove']"></el-button>
<el-button
link
type="danger"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['rs:itineraryChange:remove']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
@@ -223,24 +242,23 @@
<!-- 新增/编辑对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="900px" append-to-body :close-on-click-modal="false" destroy-on-close>
<div style="max-height: 70vh; overflow-y: auto; padding-right: 10px;">
<div style="max-height: 70vh; overflow-y: auto; padding-right: 10px">
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px">
<!-- 选择原出国申请 -->
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="选择原出国申请" prop="applicationId">
<el-select
v-model="form.applicationId"
placeholder="请选择已审批通过的出国申请"
<el-select
v-model="form.applicationId"
placeholder="请选择已审批通过的出国申请"
style="width: 100%"
@change="handleApplicationChange"
:disabled="!!form.changeId"
filterable
>
<el-option
v-for="application in availableApplicationList"
:key="application.applicationId"
<el-option
v-for="application in availableApplicationList"
:key="application.applicationId"
:label="`${application.applicantName} - ${application.destination} (${formatDate(application.startDate)}至${formatDate(application.endDate)})`"
:value="application.applicationId"
/>
@@ -336,13 +354,7 @@
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="行程安排">
<el-input
v-model="form.originalItinerary"
type="textarea"
:rows="3"
disabled
placeholder="原行程安排"
/>
<el-input v-model="form.originalItinerary" type="textarea" :rows="3" disabled placeholder="原行程安排" />
</el-form-item>
</el-col>
</el-row>
@@ -384,14 +396,7 @@
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="行程安排" prop="newItinerary">
<el-input
v-model="form.newItinerary"
type="textarea"
:rows="3"
placeholder="请输入实际行程安排"
maxlength="1000"
show-word-limit
/>
<el-input v-model="form.newItinerary" type="textarea" :rows="3" placeholder="请输入实际行程安排" maxlength="1000" show-word-limit />
</el-form-item>
</el-col>
</el-row>
@@ -401,10 +406,10 @@
<el-row :gutter="20">
<el-col :span="24">
<el-form-item prop="changeReason">
<el-input
v-model="form.changeReason"
type="textarea"
:rows="4"
<el-input
v-model="form.changeReason"
type="textarea"
:rows="4"
placeholder="请详细说明行程变更的原因(相关证明材料可附后)"
maxlength="500"
show-word-limit
@@ -418,21 +423,13 @@
<el-row :gutter="20">
<el-col :span="24">
<el-form-item>
<el-input
v-model="form.remark"
type="textarea"
:rows="3"
placeholder="请填写有关认识和态度"
maxlength="200"
show-word-limit
/>
<div style="margin-top: 12px; color: #303133; font-size: 14px; font-weight: bold;">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请填写有关认识和态度" maxlength="200" show-word-limit />
<div style="margin-top: 12px; color: #303133; font-size: 14px; font-weight: bold">
我承诺,以上情况说明完全属实。如有不实行为,愿意接受组织处理。
</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
@@ -450,14 +447,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>
@@ -468,7 +465,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>
@@ -513,7 +510,6 @@
</div>
</template>
</el-dialog>
</div>
</template>
@@ -522,7 +518,8 @@ import { ref, reactive, toRefs, onMounted, computed, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import animate from '@/animate';
import { parseTime } from '@/utils/ruoyi';
import {
import { useUserStore } from '@/store/modules/user';
import {
listItineraryChange,
listMyItineraryChange,
getItineraryChange,
@@ -535,6 +532,7 @@ import {
getOverseasApplication,
getItineraryChangeHistory,
getOverseasRecordByApplication,
listOverseasApplication,
type ItineraryChangeQuery,
type ItineraryChangeVO,
type ItineraryChangeForm,
@@ -553,7 +551,7 @@ const formRef = ref();
const state = reactive({
// Tab切换
activeTab: 'all',
// 全部变更列表
loading: false,
showSearch: true,
@@ -565,9 +563,10 @@ const state = reactive({
pageSize: 10,
applicantName: '',
destination: '',
status: ''
status: '',
applicantId: undefined as number | undefined
} as ItineraryChangeQuery,
// 我的变更列表
myLoading: false,
showMySearch: true,
@@ -577,9 +576,10 @@ const state = reactive({
pageNum: 1,
pageSize: 10,
destination: '',
status: ''
status: '',
applicantId: undefined as number | undefined
} as ItineraryChangeQuery,
// 对话框
dialog: {
visible: false,
@@ -591,21 +591,21 @@ const state = reactive({
historyDialog: {
visible: false
},
// 表单数据
form: {} as ItineraryChangeForm,
viewData: null as ItineraryChangeVO | null,
// 变更历史
historyLoading: false,
historyList: [] as ItineraryChangeVO[],
// 可用的出国申请列表(已审批通过)
availableApplicationList: [] as OverseasApplicationVO[],
// 变更类型(多选)
changeTypes: [] as string[],
// 表单验证规则
rules: {
applicationId: [{ required: true, message: '请选择原出国申请', trigger: 'change' }],
@@ -690,7 +690,7 @@ onMounted(() => {
const handleTabClick = (tab: any) => {
const tabName = tab.props?.name || tab.paneName;
state.activeTab = tabName;
if (tabName === 'all') {
getList();
} else if (tabName === 'my') {
@@ -698,18 +698,36 @@ const handleTabClick = (tab: any) => {
}
};
// 查询变更申请列表(只显示待审核的数据)
// 查询变更申请列表(只显示当前登录人的待审核和已撤回的数据)
const getList = async () => {
state.loading = true;
try {
// 如果用户没有手动选择状态,默认只显示待审核的申请
const params = { ...state.queryParams };
if (!params.status) {
params.status = '0'; // 默认只显示待审核状态
}
const userStore = useUserStore();
// 先获取所有当前登录人的数据(不分页),然后在前端过滤和分页
const params = {
pageNum: 1,
pageSize: 10000, // 设置一个较大的值,获取所有数据
applicantId: Number(userStore.userId),
applicantName: state.queryParams.applicantName,
destination: state.queryParams.destination
};
const response = await listItineraryChange(params);
state.changeList = response.rows;
state.total = response.total;
// 前端过滤:只显示状态为 0(待审核)和 3(已撤回)的数据
const filteredRows = response.rows.filter((item: ItineraryChangeVO) => {
return item.status === '0' || item.status === '3';
});
// 前端分页
const pageNum = state.queryParams.pageNum;
const pageSize = state.queryParams.pageSize;
const total = filteredRows.length;
const start = (pageNum - 1) * pageSize;
const end = Math.min(start + pageSize, total);
state.changeList = filteredRows.slice(start, end);
state.total = total;
} catch (error) {
console.error('获取变更行程列表失败:', error);
} finally {
@@ -717,13 +735,35 @@ const getList = async () => {
}
};
// 查询我的变更记录列表(只显示已通过的变更申请)
// 查询我的变更记录列表(只显示当前登录人的已通过和已拒绝的数据)
const getMyList = async () => {
state.myLoading = true;
try {
const response = await listMyItineraryChange(state.myQueryParams);
state.myChangeList = response.rows;
state.myTotal = response.total;
const userStore = useUserStore();
// 先获取所有当前登录人的数据(不分页),然后在前端过滤和分页
const params = {
pageNum: 1,
pageSize: 10000, // 设置一个较大的值,获取所有数据
applicantId: Number(userStore.userId),
destination: state.myQueryParams.destination
};
const response = await listItineraryChange(params);
// 前端过滤:只显示状态为 1(已通过)和 2(已拒绝)的数据
const filteredRows = response.rows.filter((item: ItineraryChangeVO) => {
return item.status === '1' || item.status === '2';
});
// 前端分页
const pageNum = state.myQueryParams.pageNum;
const pageSize = state.myQueryParams.pageSize;
const total = filteredRows.length;
const start = (pageNum - 1) * pageSize;
const end = Math.min(start + pageSize, total);
state.myChangeList = filteredRows.slice(start, end);
state.myTotal = total;
} catch (error) {
console.error('获取我的变更记录失败:', error);
} finally {
@@ -731,13 +771,25 @@ const getMyList = async () => {
}
};
// 获取已审批通过的出国申请列表
// 获取已审批通过的出国申请列表(只显示当前登录人的)
const getApprovedApplicationList = async () => {
try {
const response = await getApprovedOverseasApplications();
state.availableApplicationList = response.data;
const userStore = useUserStore();
// 使用listOverseasApplication查询当前登录人的已通过申请
const response = await listOverseasApplication({
pageNum: 1,
pageSize: 1000, // 设置一个较大的值,获取所有数据
applicantId: Number(userStore.userId), // 只查询当前登录人的申请
status: '1' // 只查询已通过的状态
});
// 确保只返回已通过审批的申请
state.availableApplicationList = response.rows.filter((item: OverseasApplicationVO) => {
return item.status === '1';
});
} catch (error) {
console.error('获取已审批通过的出国申请列表失败:', error);
ElMessage.error('获取已审批通过的出国申请列表失败');
}
};
@@ -798,30 +850,30 @@ const handleApplicationChange = async (applicationId: number) => {
// 1. 获取出国申请详情
const response = await getOverseasApplication(applicationId);
const selectedApplication = response.data;
// 2. 根据申请ID获取出国记录(包含recordId)
const recordResponse = await getOverseasRecordByApplication(applicationId);
const recordData = recordResponse.data;
if (selectedApplication && recordData) {
// 设置recordId(关键字段,必填)
state.form.recordId = recordData.recordId;
// 设置申请人ID(关键字段,必填)
state.form.applicantId = selectedApplication.applicantId;
// 填充本人基本情况
state.form.applicantName = selectedApplication.applicantName;
state.form.gender = selectedApplication.gender;
state.form.birthDate = selectedApplication.birthDate;
// 政治面貌:将字典值转换为文本标签
state.form.politicalStatus = getPoliticalStatusLabel(selectedApplication.politicalStatus);
state.form.politicalStatus = selectedApplication.politicalStatus ? getPoliticalStatusLabel(selectedApplication.politicalStatus) : '';
state.form.professionalTitle = selectedApplication.professionalTitle;
state.form.position = selectedApplication.position;
state.form.documentName = selectedApplication.documentName;
state.form.documentNumber = selectedApplication.documentNumber;
state.form.department = selectedApplication.department;
// 填充申请行程情况
// 前往国家:优先使用destinationCountry,其次使用destination
state.form.originalDestination = selectedApplication.destinationCountry || selectedApplication.destination;
@@ -833,7 +885,7 @@ const handleApplicationChange = async (applicationId: number) => {
state.form.originalStartDate = selectedApplication.startDate;
state.form.originalEndDate = selectedApplication.endDate;
state.form.originalDuration = selectedApplication.duration;
// 默认将原信息复制到新信息
state.form.newDestination = selectedApplication.destinationCountry || selectedApplication.destination;
state.form.newDestinationCity = selectedApplication.destinationCity;
@@ -867,22 +919,24 @@ const handleCancel = (row: ItineraryChangeVO) => {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPlaceholder: '请输入撤回原因'
}).then(async ({ value }) => {
try {
await cancelItineraryChange(row.changeId, value);
ElMessage.success('撤回成功');
// 根据当前Tab刷新列表
if (state.activeTab === 'all') {
getList();
} else if (state.activeTab === 'my') {
getMyList();
})
.then(async ({ value }) => {
try {
await cancelItineraryChange(row.changeId, value);
ElMessage.success('撤回成功');
// 根据当前Tab刷新列表
if (state.activeTab === 'all') {
getList();
} else if (state.activeTab === 'my') {
getMyList();
}
} catch (error) {
console.error('撤回失败:', error);
ElMessage.error('撤回失败');
}
} catch (error) {
console.error('撤回失败:', error);
ElMessage.error('撤回失败');
}
}).catch(() => {});
})
.catch(() => {});
};
// 重新提交
@@ -890,12 +944,12 @@ const handleResubmit = async (row: ItineraryChangeVO) => {
try {
const response = await getItineraryChange(row.changeId);
Object.assign(state.form, response.data);
// 解析变更类型
if (response.data.changeType) {
state.changeTypes = response.data.changeType.split(',');
}
state.dialog.title = '重新提交行程变更申请';
state.dialog.visible = true;
} catch (error) {
@@ -910,30 +964,32 @@ const handleDelete = (row: ItineraryChangeVO) => {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
await delItineraryChange(row.changeId);
ElMessage.success('删除成功');
// 根据当前Tab刷新列表(如果删除的是当前页最后一条且不是第1页,返回上一页)
if (state.activeTab === 'all') {
// 如果删除的是当前页最后一条且不是第1页,返回上一页
if (state.changeList.length === 1 && state.queryParams.pageNum > 1) {
state.queryParams.pageNum--;
})
.then(async () => {
try {
await delItineraryChange(row.changeId);
ElMessage.success('删除成功');
// 根据当前Tab刷新列表(如果删除的是当前页最后一条且不是第1页,返回上一页)
if (state.activeTab === 'all') {
// 如果删除的是当前页最后一条且不是第1页,返回上一页
if (state.changeList.length === 1 && state.queryParams.pageNum > 1) {
state.queryParams.pageNum--;
}
getList();
} else if (state.activeTab === 'my') {
// 如果删除的是当前页最后一条且不是第1页,返回上一页
if (state.myChangeList.length === 1 && state.myQueryParams.pageNum > 1) {
state.myQueryParams.pageNum--;
}
getMyList();
}
getList();
} else if (state.activeTab === 'my') {
// 如果删除的是当前页最后一条且不是第1页,返回上一页
if (state.myChangeList.length === 1 && state.myQueryParams.pageNum > 1) {
state.myQueryParams.pageNum--;
}
getMyList();
} catch (error) {
console.error('删除失败:', error);
ElMessage.error('删除失败');
}
} catch (error) {
console.error('删除失败:', error);
ElMessage.error('删除失败');
}
}).catch(() => {});
})
.catch(() => {});
};
// 查看变更历史
@@ -963,15 +1019,14 @@ const submitForm = () => {
if (state.form.newExitReason !== state.form.originalExitReason) {
changeTypeList.push('出境事由变更');
}
if (state.form.newStartDate !== state.form.originalStartDate ||
state.form.newEndDate !== state.form.originalEndDate) {
if (state.form.newStartDate !== state.form.originalStartDate || state.form.newEndDate !== state.form.originalEndDate) {
changeTypeList.push('时间变更');
}
if (state.form.newItinerary && state.form.newItinerary !== state.form.originalItinerary) {
changeTypeList.push('行程变更');
}
state.form.changeType = changeTypeList.length > 0 ? changeTypeList.join(',') : '其他变更';
state.submitLoading = true;
try {
if (state.form.changeId) {
@@ -982,7 +1037,7 @@ const submitForm = () => {
ElMessage.success('提交成功!变更申请已进入审批流程。');
}
state.dialog.visible = false;
// 根据当前Tab刷新列表(重置到第1页)
if (state.activeTab === 'all') {
state.queryParams.pageNum = 1;
@@ -1056,11 +1111,11 @@ 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', // 已拒绝
'3': 'info' // 已撤回
};
return typeMap[status] || 'info';
};
</script>
<style scoped>
@@ -1081,6 +1136,8 @@ const getStatusTagType = (status: string): 'primary' | 'success' | 'info' | 'war
.table-card {
margin-bottom: 10px;
}
:global(.app-container) {
padding: 0 !important;
}
</style>
+190 -47
View File
@@ -49,24 +49,24 @@
</el-card>
</div>
<!-- 操作按钮区域 -->
<el-card shadow="never" class="button-card table-card">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Refresh" @click="handleRefreshPending">刷新</el-button>
</el-col>
<el-col :span="1.5">
<div class="info-text">
<el-icon><InfoFilled /></el-icon>
<span>这里显示所有待审批申请</span>
</div>
</el-col>
</el-row>
</template>
<!-- 操作按钮区域 -->
<el-card shadow="never" class="button-card table-card">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Refresh" @click="handleRefreshPending">刷新</el-button>
</el-col>
<el-col :span="1.5">
<div class="info-text">
<el-icon><InfoFilled /></el-icon>
<span>这里显示所有待审批申请</span>
</div>
</el-col>
</el-row>
</template>
<!-- 表格 -->
<el-table v-loading="pendingLoading" :data="pendingList" border>
<!-- 表格 -->
<el-table v-loading="pendingLoading" :data="pendingList" border>
<el-table-column label="申请人" prop="applicantName" min-width="100" align="center" :show-overflow-tooltip="true" />
<el-table-column label="所属部门" prop="department" min-width="140" align="center" :show-overflow-tooltip="true" />
<el-table-column label="前往国家" prop="destinationCountry" min-width="110" align="center" :show-overflow-tooltip="true" />
@@ -350,8 +350,16 @@
<template #footer>
<div style="display: flex; justify-content: space-between; width: 100%">
<div>
<!-- 只有审批完成的申请才显示下载PDF按钮 -->
<el-button v-if="currentDetail && currentDetail.status === '1'" type="primary" icon="Download" @click="handleDownloadPdf" v-hasPermi="['rs:overseas:query']">下载审批表PDF</el-button>
<!-- 只有审批完成的申请才显示下载Word按钮 -->
<el-button
v-if="currentDetail && currentDetail.status === '1'"
type="primary"
icon="Download"
@click="handleDownloadApprovalWord"
v-hasPermi="['rs:overseas:query']"
>
下载审批表Word
</el-button>
</div>
<div>
<el-button @click="detailDialogVisible = false">关闭</el-button>
@@ -362,14 +370,15 @@
</div>
</template>
<script setup name="OverseasApprovalMonitor">
<script setup lang="ts" name="OverseasApprovalMonitor">
import { ref, reactive, onMounted, watch } from 'vue';
import dayjs from 'dayjs';
import { ElMessage, ElMessageBox } from 'element-plus';
import { useRouter } from 'vue-router';
import { Clock, Search, Refresh, View, EditPen, InfoFilled, Download } from '@element-plus/icons-vue';
// API请求
import { getPendingList as apiGetPendingList, getCompletedList as apiGetCompletedList, getDetail, downloadApprovalPdf } from '@/api/rs/overseas';
import { getPendingList as apiGetPendingList, getCompletedList as apiGetCompletedList, getDetail } from '@/api/rs/overseas';
// 导入变更历史API接口
import { getItineraryChangeHistory } from '@/api/rs/overseas';
@@ -405,6 +414,8 @@ const completedQueryParams = reactive({
department: undefined,
destination: undefined,
applyTimeRange: undefined,
applyTimeStart: undefined as string | undefined,
applyTimeEnd: undefined as string | undefined,
status: '1'
});
@@ -691,48 +702,180 @@ const getChangeStatusTagType = (status) => {
};
/**
* 下载审批表PDF
* 下载审批表Word
*/
const handleDownloadPdf = async () => {
if (!currentDetail.value || !currentDetail.value.applicationId) {
const handleDownloadApprovalWord = async () => {
const detail = currentDetail.value;
if (!detail || !detail.applicationId) {
ElMessage.error('无法获取申请ID');
return;
}
try {
ElMessage.info('正在生成PDF,请稍候...');
// 调用API获取blob数据
const response = await downloadApprovalPdf(currentDetail.value.applicationId);
// 创建带MIME类型的Blob对象
const blob = response instanceof Blob
? new Blob([response], { type: 'application/pdf' })
: new Blob([response.data], { type: 'application/pdf' });
// 创建下载链接
ElMessage.info('正在生成审批表,请稍候...');
const templateData = buildApprovalTemplateData(detail);
const [Docxtemplater, PizZip, ImageModule] = await Promise.all([
import('docxtemplater'),
import('pizzip'),
import('docxtemplater-image-module-free')
]);
const templateResponse = await fetch('/chuguojingshenpibiao.docx', { cache: 'no-store' });
if (!templateResponse.ok) {
throw new Error('模板文件加载失败,请联系管理员');
}
const arrayBuffer = await templateResponse.arrayBuffer();
const zip = new PizZip.default(arrayBuffer);
const imageOpts = {
centered: false,
getImage: (base64: string) => {
if (!base64) {
return null;
}
const pure = base64.split(',')[1] || base64;
const bytes = Uint8Array.from(atob(pure), (char) => char.charCodeAt(0));
return bytes.buffer;
},
getSize: () => [60, 20]
};
const doc = new Docxtemplater.default(zip, {
modules: [new ImageModule.default(imageOpts)],
paragraphLoop: true,
linebreaks: true,
delimiters: { start: '{{', end: '}}' },
parser: (tag: string) => {
return {
get: (scope: Record<string, any>) => {
const cleanTag = tag.trim().replace(/^%+/, '');
return scope[cleanTag];
}
};
}
});
doc.render(templateData);
const blob = doc.getZip().generate({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
const fileName = `出国申请审批表-${detail.applicantName || dayjs().format('YYYYMMDDHHmmss')}.docx`;
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
// 设置下载文件名(使用申请人姓名和时间戳)
const fileName = `出国申请审批表_${currentDetail.value.applicantName}_${currentDetail.value.applicationId}.pdf`;
link.download = fileName;
// 触发下载
document.body.appendChild(link);
link.click();
// 清理资源
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
ElMessage.success('PDF下载成功');
} catch (error) {
console.error('下载PDF失败', error);
ElMessage.error('下载PDF失败,请稍后重试');
ElMessage.success('审批表下载成功');
} catch (error: any) {
console.error('下载审批表失败', error);
ElMessage.error(error?.message || '下载审批表失败,请稍后重试');
}
};
/**
* 构建审批表模板数据
*/
const buildApprovalTemplateData = (detail) => {
const startParts = getDateParts(detail.startDate);
const endParts = getDateParts(detail.endDate);
const applyParts = getDateParts(detail.applyTime || detail.createTime);
const durationDays = detail.duration || calculateDuration(detail.startDate, detail.endDate);
const templateData: Record<string, any> = {
applicantName: detail.applicantName || '',
genderText: detail.genderText || '',
birthYear: getDateParts(detail.birthDate).year,
birthMonth: getDateParts(detail.birthDate).month,
birthDay: getDateParts(detail.birthDate).day,
politicalStatusText: detail.politicalStatusText || '-',
department: detail.department || '-',
professionalTitle: detail.professionalTitle || '-',
position: detail.position || '-',
documentName: detail.documentName || '-',
documentNumber: detail.documentNumber || '-',
destinationCountry: detail.destinationCountry || detail.destination || '-',
isTourismChecked: detail.exitReason === '旅游' ? '√' : '',
isVisitChecked: detail.exitReason === '探亲' ? '√' : '',
visitRelationship: detail.visitRelationship || '',
remarks: detail.remarks || '',
startYear: startParts.year,
startMonth: startParts.month,
startDay: startParts.day,
endYear: endParts.year,
endMonth: endParts.month,
endDay: endParts.day,
durationDays: durationDays || '',
applicantSignature: detail.signature || '',
mobilePhone: detail.mobilePhone || detail.contactPhone || '',
emergencyContact: detail.emergencyContact || '',
applicantSignYear: applyParts.year,
applicantSignMonth: applyParts.month,
applicantSignDay: applyParts.day
};
fillApprovalNodeData(templateData, detail.approvalHistory || []);
return templateData;
};
/**
* 计算出国天数
*/
const calculateDuration = (startDate, endDate) => {
if (!startDate || !endDate) return '';
const start = dayjs(startDate);
const end = dayjs(endDate);
if (!start.isValid() || !end.isValid()) return '';
return Math.max(end.diff(start, 'day') + 1, 1);
};
/**
* 获取日期拆分
*/
const getDateParts = (date) => {
if (!date) {
return { year: '', month: '', day: '' };
}
const parsed = dayjs(date);
if (!parsed.isValid()) {
return { year: '', month: '', day: '' };
}
return {
year: parsed.format('YYYY'),
month: parsed.format('MM'),
day: parsed.format('DD')
};
};
/**
* 填充审批节点数据
*/
const fillApprovalNodeData = (templateData, approvalHistory) => {
const approvalNodes = [
{ level: '1', prefix: 'deptLeader' },
{ level: '2', prefix: 'hr' },
{ level: '3', prefix: 'discipline' },
{ level: '4', prefix: 'viceDean' },
{ level: '5', prefix: 'secretary' }
];
approvalNodes.forEach((node) => {
const record = approvalHistory.find((item) => item.approvalLevel === node.level);
const dateParts = getDateParts(record?.approvalTime);
templateData[`${node.prefix}Signature`] = record?.approvalSignature || record?.signature || '';
templateData[`${node.prefix}Opinion`] = record?.approvalOpinion || '';
templateData[`${node.prefix}Year`] = dateParts.year;
templateData[`${node.prefix}Month`] = dateParts.month;
templateData[`${node.prefix}Day`] = dateParts.day;
});
};
</script>
<style scoped lang="scss">
@@ -1,88 +0,0 @@
# 出国申请进度查询功能修改总结 - 快速参考
## ✅ 修改完成
**修改时间**:2025-10-29
**修改范围**:仅修改monitor相关文件,未影响其他模块
---
## 📋 核心修改
### 1. 功能变化
#### 修改前
- ❌ 显示工作流迁移提示
- ❌ 无实际查询功能
- ❌ 表格被隐藏
#### 修改后
- ✅ 显示当前用户提交的待审批申请
- ✅ 显示当前用户提交的已完成审批申请(抄送)
- ✅ 支持搜索和查看审批历史
---
## 🎯 核心逻辑
### 数据过滤
```java
// 后端Service层添加过滤条件
Long currentUserId = LoginHelper.getUserId();
wrapper.eq("applicant_id", currentUserId); // 🔴 只查询当前用户提交的申请
```
### 功能说明
- **待审批中**:用户提交申请后,在这里查看审批进度
- **审批完成**:审批流程完成后,抄送一份到这里
---
## 📄 修改文件
| 文件 | 类型 | 修改内容 |
|------|------|---------|
| `RsOverseasApprovalMonitorServiceImpl.java` | 后端 | 添加当前用户过滤 |
| `RsOverseasApprovalMonitorController.java` | 后端 | 恢复pending接口 |
| `monitor/index.vue` | 前端 | 删除提示,恢复表格 |
---
## 🧪 快速测试
### 测试步骤
1. 登录系统
2. 访问"出国申请进度查询"
3. 查看"待审批中"和"审批完成"选项卡
### 预期结果
- ✅ 只显示当前用户提交的申请
- ✅ 不显示其他用户的申请
- ✅ 可以查看审批历史
---
## 🚀 部署命令
```bash
# 后端
cd backend
mvn clean package -pl ruoyi-admin -am -DskipTests
./stop-main.sh && ./start-main.sh
# 前端(如需要)
cd web
npm run build
```
---
## 📝 详细文档
完整修改说明请参考:`出国申请进度查询功能修改说明.md`
---
**状态**:✅ 已完成
**版本**:v1.0
@@ -1,365 +0,0 @@
# 出国申请进度查询功能优化说明
**日期**: 2025-01-29
**模块**: 出国申请进度查询 (`/rs/overseas/monitor`)
## 📋 功能需求
### 1. 变更历史记录条件显示
- **需求**: 变更历史记录字段只在有变更记录时显示,没有变更记录时不显示该区域
- **实现位置**: `web/src/views/rs/overseas/monitor/index.vue`
### 2. PDF下载功能
- **需求**: 在审批完成选项卡的详情弹窗中新增PDF下载按钮
- **PDF模板**: 杭州第一技师学院登记报备人员因私事出国(境)审批表
- **显示条件**: 仅当申请状态为"已通过"(status='1')时显示下载按钮
## 🎯 实现内容
### 一、前端修改
#### 1. 变更历史记录条件显示
**文件**: `web/src/views/rs/overseas/monitor/index.vue`
**修改内容**:
- 原条件: `v-if="currentDetail.recordId"`
- 新条件: `v-if="currentDetail.recordId && changeHistoryList.length > 0"`
- 移除空状态提示组件 `<el-empty>`
**代码位置**: 第287行
```vue
<!-- 变更历史记录(仅已完成且有变更记录时显示) -->
<div v-if="currentDetail.recordId && changeHistoryList.length > 0" style="margin-top: 20px">
<el-divider content-position="left">
<span style="font-size: 16px; font-weight: bold; color: #409eff">
<el-icon style="vertical-align: middle; margin-right: 5px"><EditPen /></el-icon>
变更历史记录
</span>
</el-divider>
<!-- 变更历史列表 -->
<div v-loading="changeHistoryLoading">
<el-table :data="changeHistoryList" border style="width: 100%" max-height="400px">
<!-- 表格列定义 -->
</el-table>
</div>
</div>
```
#### 2. PDF下载按钮
**文件**: `web/src/views/rs/overseas/monitor/index.vue`
**新增内容**:
1. **导入图标**: 添加 `Download` 图标
2. **导入API**: 添加 `downloadApprovalPdf` 方法
3. **弹窗footer布局**: 改为左右布局,左侧显示下载按钮,右侧显示关闭按钮
4. **下载方法**: 添加 `handleDownloadPdf` 方法
**代码位置**: 第350-360行
```vue
<template #footer>
<div style="display: flex; justify-content: space-between; width: 100%">
<div>
<!-- 只有审批完成的申请才显示下载PDF按钮 -->
<el-button
v-if="currentDetail && currentDetail.status === '1'"
type="primary"
icon="Download"
@click="handleDownloadPdf"
v-hasPermi="['rs:overseas:query']">
下载审批表PDF
</el-button>
</div>
<div>
<el-button @click="detailDialogVisible = false">关闭</el-button>
</div>
</div>
</template>
```
**JavaScript方法** (第693-710行):
```javascript
/**
* 下载审批表PDF
*/
const handleDownloadPdf = async () => {
if (!currentDetail.value || !currentDetail.value.applicationId) {
ElMessage.error('无法获取申请ID');
return;
}
try {
ElMessage.info('正在生成PDF,请稍候...');
await downloadApprovalPdf(currentDetail.value.applicationId);
ElMessage.success('PDF下载成功');
} catch (error) {
console.error('下载PDF失败', error);
ElMessage.error('下载PDF失败,请稍后重试');
}
};
```
#### 3. API接口定义
**文件**: `web/src/api/rs/overseas.ts`
**新增内容**: Monitor相关API接口(第1036-1085行)
```typescript
// ==================== 出国申请进度查询(监控)相关API ====================
// 审批监控查询参数类型
export interface OverseasMonitorQuery {
pageNum: number;
pageSize: number;
applicantName?: string;
department?: string;
destination?: string;
approvalLevel?: string;
currentApprover?: string;
status?: string;
applyTimeStart?: string;
applyTimeEnd?: string;
}
// 查询待审批列表(监控)
export function getPendingList(query: OverseasMonitorQuery) {
return request({
url: '/rs/overseas/monitor/pending/list',
method: 'get',
params: query
});
}
// 查询已完成审批列表(监控)
export function getCompletedList(query: OverseasMonitorQuery) {
return request({
url: '/rs/overseas/monitor/completed/list',
method: 'get',
params: query
});
}
// 查询申请详情(监控)
export function getDetail(applicationId: string | number) {
return request({
url: `/rs/overseas/monitor/detail/${applicationId}`,
method: 'get'
});
}
// 下载审批表PDF
export function downloadApprovalPdf(applicationId: string | number) {
return request({
url: `/rs/overseas/monitor/downloadApprovalPdf/${applicationId}`,
method: 'get',
responseType: 'blob'
});
}
```
### 二、后端修改
#### 1. Service接口
**文件**: `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApprovalMonitorService.java`
**新增方法**:
```java
/**
* 下载出国申请审批表PDF
*
* @param applicationId 申请ID
* @param response HTTP响应对象
*/
void downloadApprovalPdf(Long applicationId, HttpServletResponse response);
```
#### 2. Controller接口
**文件**: `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java`
**新增接口** (第119-135行):
```java
/**
* 下载出国申请审批表PDF
* <p>
* 生成并下载"杭州第一技师学院登记报备人员因私事出国(境)审批表"PDF文件
*
* @param applicationId 申请ID
* @param response HTTP响应对象
*/
@SaCheckPermission("rs:overseas:query")
@GetMapping("/downloadApprovalPdf/{applicationId}")
public void downloadApprovalPdf(
@PathVariable @NotNull(message = "申请ID不能为空") Long applicationId,
HttpServletResponse response) {
log.info("下载出国申请审批表PDF,申请ID:{}", applicationId);
monitorService.downloadApprovalPdf(applicationId, response);
}
```
#### 3. Service实现
**文件**: `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java`
**新增依赖导入**:
```java
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.common.core.exception.ServiceException;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
```
**新增方法** (第521-808行):
1. **`downloadApprovalPdf`**: 主方法,负责查询数据和设置响应头
2. **`generateApprovalPdf`**: PDF生成核心方法
3. **`addCell`**: 添加表格单元格辅助方法
4. **`addApprovalRow`**: 添加审批意见行辅助方法
5. **`getApprovalOpinion`**: 获取指定级别审批意见
6. **`formatDate`**: 格式化日期(yyyy-MM-dd)
7. **`formatBirthDate`**: 格式化出生日期(yyyy年MM月)
**PDF文档结构**:
1. 标题:
- "杭州第一技师学院"
- "登记报备人员因私事出国(境)审批表"
2. 个人信息表格(8列):
- 姓名、性别、出生年月、政治面貌
- 现工作部门(教学系)、职称、职务
- 申领证件名称、证件号码
- 前往国家或地区、出境事由(旅游/探亲)
- 探望对象姓名及与申请者关系
- 出国时间、备注
- 承诺声明、联系电话、应急联系人
3. 审批意见表格(2列):
- 部门(教学系)意见
- 人事(组织)处审核意见
- 纪委意见
- 学院分管领导意见
- 学院党委书记意见
4. 备注说明:
- 因私出境时间说明
- 证件号码填写说明
## 🔧 技术实现细节
### PDF生成技术
- **库**: iText 5.5.13.3
- **中文字体**: STSong-Light (UniGB-UCS2-H)
- **页面尺寸**: A4
- **页边距**: 40pt (上下左右)
### 字体大小
- **标题字体**: 16pt, 加粗
- **副标题字体**: 14pt, 加粗
- **正文字体**: 10pt, 常规
- **小字体**: 9pt, 常规
### 表格设计
- **个人信息表**: 8列,列宽比例 [8:12:6:10:10:12:16:6]
- **审批意见表**: 2列,列宽比例 [20:80]
- **单元格最小高度**: 25-50pt
- **表头背景**: 浅灰色 (BaseColor.LIGHT_GRAY)
### 数据处理
- **性别翻译**: 0→男, 1→女
- **政治面貌翻译**: 支持多种编码格式(01-13, 1-13, 英文代码)
- **出境事由**: 旅游/探亲,用√标记
- **日期格式化**: yyyy-MM-dd / yyyy年MM月
- **审批意见**: 优先显示审批意见,无意见时通过状态显示"同意"
## 📝 权限要求
- **查看权限**: `rs:overseas:query`
- **列表权限**: `rs:overseas:list`
## 🎨 UI/UX优化
1. **按钮位置**: 弹窗footer左右布局,下载按钮在左侧,关闭按钮在右侧
2. **按钮样式**: 主色调按钮(type="primary")+ 下载图标
3. **显示条件**: 仅审批完成状态显示下载按钮
4. **用户提示**:
- 开始下载:"正在生成PDF,请稍候..."
- 下载成功:"PDF下载成功"
- 下载失败:"下载PDF失败,请稍后重试"
## ✅ 测试要点
### 前端测试
1. ✅ 有变更记录时,变更历史区域正常显示
2. ✅ 无变更记录时,变更历史区域不显示
3. ✅ 审批完成状态显示下载PDF按钮
4. ✅ 待审批状态不显示下载PDF按钮
5. ✅ 点击下载按钮能触发PDF下载
6. ✅ 下载过程中显示加载提示
### 后端测试
1. ✅ PDF文件名包含申请人姓名和时间戳
2. ✅ PDF包含完整的个人信息
3. ✅ PDF包含所有审批历史记录
4. ✅ 中文字体正确显示
5. ✅ 表格格式符合模板要求
6. ✅ 审批意见正确填充
7. ✅ 日期格式化正确
## 📊 文件清单
### 前端文件
- ✅ `web/src/views/rs/overseas/monitor/index.vue` (修改)
- ✅ `web/src/api/rs/overseas.ts` (修改,新增monitor相关API)
### 后端文件
- ✅ `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApprovalMonitorService.java` (修改)
- ✅ `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java` (修改)
- ✅ `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java` (修改)
### 文档文件
- ✅ `web/src/views/rs/overseas/monitor/出国申请进度查询功能优化说明.md` (新增)
## 🚀 部署说明
### 前端部署
```bash
cd web
npm run build
# 部署dist目录到Nginx
```
### 后端部署
```bash
cd backend
mvn clean package -DskipTests
# 重启ruoyi-admin服务
```
### 依赖检查
后端已包含iText PDF生成库依赖:
- `com.itextpdf:itextpdf:5.5.13.3`
- `com.itextpdf:itext-asian:5.2.0` (中文字体支持)
## 📖 相关文档
- **项目规范**: `.cursor/cursorrules.md`
- **开发文档**: `docs/开发文档/API/`
- **出国申请API**: `docs/开发文档/API/出国申请管理API.md`
## 🔗 相关模块
- **出国申请管理**: `/rs/overseas/application`
- **出国记录管理**: `/rs/overseas/record`
- **行程变更管理**: `/rs/itinerary/change`
---
**实施人**: Cursor AI
**审核状态**: ✅ 已完成
**更新日期**: 2025-01-29
@@ -1,394 +0,0 @@
# 出国申请进度查询功能修改说明
## 📋 修改概述
**修改时间**:2025-10-29
**修改目的**:按照审批流程要求,修改出国申请进度查询功能,实现申请人查看自己提交的申请进度
---
## 🎯 功能需求
### 1. 待审批选项卡
- **功能**:用户提交申请后,在这里查看自己的待审批记录
- **显示内容**:
- 当前用户提交的所有待审批申请
- 当前审批环节和审批人
- 审批历史记录
### 2. 审批完成选项卡
- **功能**:审批完成后,抄送一份到这里
- **显示内容**:
- 当前用户提交的所有已完成审批申请
- 完整的审批历史记录
- 关联的出国记录信息
---
## 📝 修改内容
### 1. 后端Service修改
**文件**:`backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java`
#### 修改点1:待审批列表查询
```java
/**
* 查询审批中的申请列表(待审核状态)
* 🔴 只查询当前用户提交的待审批申请
*/
@Override
public TableDataInfo<RsOverseasApprovalMonitorVo> queryPendingList(RsOverseasApprovalMonitorQuery query) {
// 🔴 获取当前登录用户ID
Long currentUserId = LoginHelper.getUserId();
// 构建查询条件
QueryWrapper<RsOverseasApplication> wrapper = buildQueryWrapper(query);
wrapper.eq("status", "0"); // 只查询待审核状态
wrapper.eq("applicant_id", currentUserId); // 🔴 只查询当前用户提交的申请
// ... 分页查询和数据处理
}
```
**修改说明**:
- ✅ 添加当前用户ID过滤条件
- ✅ 只查询申请人ID = 当前用户ID的记录
- ✅ 添加日志记录便于排查问题
#### 修改点2:已完成列表查询
```java
/**
* 查询已完成审批的申请列表(已通过状态)
* 🔴 只查询当前用户提交的已完成审批申请(抄送功能)
*/
@Override
public TableDataInfo<RsOverseasApprovalMonitorVo> queryCompletedList(RsOverseasApprovalMonitorQuery query) {
// 🔴 获取当前登录用户ID
Long currentUserId = LoginHelper.getUserId();
// 构建查询条件
QueryWrapper<RsOverseasApplication> wrapper = buildQueryWrapper(query);
wrapper.eq("status", "1"); // 只查询已通过状态
wrapper.eq("applicant_id", currentUserId); // 🔴 只查询当前用户提交的申请
// ... 分页查询和数据处理
}
```
**修改说明**:
- ✅ 实现抄送功能:审批完成后,申请人可以在这里查看
- ✅ 添加当前用户ID过滤条件
- ✅ 只显示当前用户提交且已完成审批的申请
---
### 2. 后端Controller修改
**文件**:`backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java`
#### 修改点:恢复pending接口功能
```java
/**
* 查询待审批的申请列表(出国申请进度查询)
* 🔴 功能说明:查询当前用户提交的待审批申请
*/
@SaCheckPermission("rs:overseas:list")
@GetMapping("/pending/list")
public TableDataInfo<RsOverseasApprovalMonitorVo> pendingList(RsOverseasApprovalMonitorQuery query) {
log.info("查询当前用户的待审批申请列表");
query.setStatus("0");
return monitorService.queryPendingList(query);
}
```
**修改说明**:
- ❌ 删除了工作流迁移提示
- ✅ 恢复正常的查询功能
- ✅ 返回实际数据而不是错误提示
---
### 3. 前端页面修改
**文件**:`web/src/views/rs/overseas/monitor/index.vue`
#### 修改点1:删除工作流迁移提示
```vue
<!-- 🔴 删除的内容 -->
<el-alert
title="功能迁移通知"
description="出国申请监控功能已标准化..."
...
>
<el-button @click="navigateToTaskWaiting">前往待办任务页面</el-button>
</el-alert>
```
**修改说明**:
- ❌ 删除整个Alert提示组件
- ❌ 删除跳转到工作流页面的按钮
#### 修改点2:恢复待审批表格显示
```vue
<!-- 表格 -->
<el-table v-loading="pendingLoading" :data="pendingList" border>
<!-- 之前是 style="display: none" 隐藏的,现在恢复显示 -->
<el-table-column label="申请人" prop="applicantName" ... />
<el-table-column label="所属部门" prop="department" ... />
<!-- ... 其他列 -->
</el-table>
```
**修改说明**:
- ✅ 恢复表格正常显示
- ✅ 删除 `style="display: none"`
- ✅ 显示待审批申请数据
#### 修改点3:恢复查询功能
```javascript
// 🔴 修改前:显示提示信息
const getPendingList = async () => {
ElMessage.info('请使用标准工作流待办页面查看审批任务');
pendingList.value = [];
};
// ✅ 修改后:正常查询数据
const getPendingList = async () => {
pendingLoading.value = true;
try {
const response = await apiGetPendingList(pendingQueryParams);
if (response.code === 200) {
pendingList.value = response.rows || [];
pendingTotal.value = response.total || 0;
} else {
ElMessage.error(response.msg || '获取数据失败');
}
} catch (error) {
console.error('获取待审批列表失败', error);
ElMessage.error('获取数据失败,请稍后重试');
} finally {
pendingLoading.value = false;
}
};
```
**修改说明**:
- ✅ 调用真实API接口
- ✅ 处理loading状态
- ✅ 显示查询结果或错误提示
#### 修改点4:恢复查询和重置按钮功能
```javascript
// 查询按钮
const handlePendingQuery = () => {
pendingQueryParams.pageNum = 1;
getPendingList();
};
// 重置按钮
const resetPendingQuery = () => {
pendingQueryParams.pageNum = 1;
pendingQueryParams.applicantName = undefined;
pendingQueryParams.department = undefined;
pendingQueryParams.destination = undefined;
pendingQueryParams.approvalLevel = undefined;
getPendingList();
};
```
**修改说明**:
- ✅ 恢复正常的查询功能
- ✅ 恢复正常的重置功能
- ❌ 删除工作流跳转提示
#### 修改点5:添加功能说明
```vue
<el-col :span="1.5">
<div class="info-text">
<el-icon><InfoFilled /></el-icon>
<span>这里显示您提交的待审批申请</span>
</div>
</el-col>
```
**修改说明**:
- ✅ 在操作按钮区域添加功能说明
- ✅ 明确告知用户这里只显示自己提交的申请
- ✅ 添加对应的CSS样式
---
## 🎨 UI变化
### 修改前
```
[Alert提示框:功能迁移通知]
[前往待办任务页面按钮]
[Empty: 请使用标准工作流待办页面]
```
### 修改后
```
[搜索条件]
[刷新按钮] [ℹ️ 这里显示您提交的待审批申请]
[数据表格:显示待审批申请列表]
[分页组件]
```
---
## 🔍 数据过滤逻辑
### 待审批选项卡
```sql
SELECT * FROM rs_overseas_application
WHERE status = '0' -- 待审核状态
AND applicant_id = {当前用户ID} -- 🔴 只查询当前用户提交的
AND deleted = '0'
AND tenant_id = {租户ID}
ORDER BY apply_time DESC
```
### 审批完成选项卡
```sql
SELECT * FROM rs_overseas_application
WHERE status = '1' -- 已通过状态
AND applicant_id = {当前用户ID} -- 🔴 只查询当前用户提交的
AND deleted = '0'
AND tenant_id = {租户ID}
ORDER BY apply_time DESC
```
---
## 🧪 测试验证
### 测试场景1:待审批选项卡
**测试步骤**:
1. 使用用户A登录系统
2. 提交一个出国申请
3. 访问"出国申请进度查询"菜单
4. 切换到"待审批中"选项卡
**预期结果**:
- ✅ 能看到刚才提交的申请记录
- ✅ 显示当前审批环节和审批人
- ✅ 点击"查看详情"能查看审批历史
**测试步骤2**:
1. 使用用户B登录系统
2. 访问"出国申请进度查询"菜单
3. 切换到"待审批中"选项卡
**预期结果**:
- ✅ 只能看到用户B提交的申请
- ❌ 看不到用户A提交的申请
### 测试场景2:审批完成选项卡
**测试步骤**:
1. 使用用户A登录系统
2. 提交一个出国申请
3. 完成全部审批流程
4. 访问"出国申请进度查询"菜单
5. 切换到"审批完成"选项卡
**预期结果**:
- ✅ 能看到刚才审批完成的申请(抄送)
- ✅ 显示完整的审批历史记录
- ✅ 能查看关联的出国记录信息
### 测试场景3:搜索功能
**测试步骤**:
1. 在"待审批中"选项卡输入搜索条件
2. 点击"搜索"按钮
**预期结果**:
- ✅ 能正常搜索并返回结果
- ✅ 结果仍然只包含当前用户提交的申请
---
## 📊 修改文件清单
| 序号 | 文件类型 | 文件路径 | 修改内容 | 状态 |
|------|---------|---------|---------|------|
| 1 | 后端Service | `RsOverseasApprovalMonitorServiceImpl.java` | 添加当前用户过滤条件 | ✅ |
| 2 | 后端Controller | `RsOverseasApprovalMonitorController.java` | 恢复pending接口功能 | ✅ |
| 3 | 前端页面 | `monitor/index.vue` | 删除工作流提示,恢复表格显示 | ✅ |
| 4 | 前端脚本 | `monitor/index.vue` | 恢复查询、重置功能 | ✅ |
---
## ⚠️ 注意事项
### 1. 权限要求
- 用户需要有 `rs:overseas:list` 权限才能访问此菜单
- 用户需要有 `rs:overseas:query` 权限才能查看详情
### 2. 数据范围
- **待审批选项卡**:只显示当前用户提交的待审核申请
- **审批完成选项卡**:只显示当前用户提交的已完成申请
- 不会显示其他用户提交的申请
### 3. 与其他模块的关系
- **出国申请**:用户在这里提交申请
- **出国审批**:审批人在这里审批申请
- **出国记录**:审批完成后,申请人在这里查看出国记录
- **出国申请进度查询**:申请人在这里查看申请进度(当前模块)
---
## 🚀 部署说明
### 后端部署
```bash
cd backend
mvn clean package -pl ruoyi-admin -am -DskipTests
./stop-main.sh
./start-main.sh
```
### 前端部署
```bash
cd web
npm run build
# 将dist目录部署到nginx
```
### 验证步骤
1. 清除浏览器缓存
2. 重新登录系统
3. 访问"出国申请进度查询"菜单
4. 确认功能正常
---
## ✅ 验收标准
- [ ] 待审批选项卡只显示当前用户提交的待审批申请
- [ ] 审批完成选项卡只显示当前用户提交的已完成审批申请
- [ ] 搜索功能正常工作
- [ ] 重置功能正常工作
- [ ] 查看详情能显示审批历史
- [ ] 不会显示其他用户的申请
- [ ] UI显示正常,无工作流迁移提示
---
## 📞 问题反馈
如有问题,请检查:
1. 后端日志:`backend/logs/sys-info.log`
2. 浏览器控制台:F12查看网络请求和错误
3. 确认用户权限配置正确
---
**文档版本**:v1.0
**最后更新**:2025-10-29
**修改人员**:Cursor AI
@@ -1,411 +0,0 @@
# 出国申请审批环节命名标准分析报告
## 📋 问题发现
在分析代码时,发现系统中**存在两套不同的审批级别命名标准**:
1. **出国申请进度查询模块**:使用**3级审批**命名
2. **出国申请审批模块**:使用**5级审批**命名
这导致**命名不一致**的问题。
---
## 🔍 详细分析
### 一、出国申请进度查询模块(monitor)
**文件位置**:`RsOverseasApprovalMonitorServiceImpl.java`
**当前定义**(第390-404行):
```java
private String getApprovalLevelText(String level) {
if (level == null) {
return "未开始";
}
switch (level) {
case "1":
return "部门主管审批";
case "2":
return "分管领导审批";
case "3":
return "校领导审批";
default:
return "未知";
}
}
```
**问题**:
- ❌ 只定义了3个级别
- ❌ Level 4 和 Level 5 会显示为"未知"
- ❌ 命名与实际审批流程不符
---
### 二、出国申请审批模块(application)
**文件位置**:`RsOverseasApplicationServiceImpl.java`
**当前定义**(第1269-1283行):
```java
private String getApprovalLevelDescription(String approvalLevel) {
switch (approvalLevel) {
case "1":
return "待部门负责人审批";
case "2":
return "待人事处审批";
case "3":
return "待纪委审批";
case "4":
return "待学院分管领导审批";
case "5":
return "待学院党委书记审批";
default:
return "待审批";
}
}
```
**流转逻辑**(第683-698行):
```java
/**
* 获取下一级审批级别
* 五级审批制度:1-部门负责人,2-人事处,3-纪委,4-学院分管领导,5-学院党委书记
*/
private String getNextApprovalLevel(String currentLevel) {
switch (currentLevel) {
case "1":
return "2"; // 部门负责人 → 人事处
case "2":
return "3"; // 人事处 → 纪委
case "3":
return "4"; // 纪委 → 学院分管领导
case "4":
return "5"; // 学院分管领导 → 学院党委书记
case "5":
return null; // 学院党委书记 → 审批完成
default:
return null;
}
}
```
---
## 📊 标准审批级别定义
### 完整的五级审批制度
| 审批级别 | 数据库值 | 权限标识 | 审批环节名称 | 当前审批人显示 | 说明 |
|---------|---------|---------|------------|--------------|------|
| **Level 1** | `"1"` | `rs:overseas:approve1` | **部门负责人审批** | 待部门负责人审批 | 只能看到本部门的申请 |
| **Level 2** | `"2"` | `rs:overseas:approve2` | **人事处审批** | 待人事处审批 | 可以看到所有部门的申请 |
| **Level 3** | `"3"` | `rs:overseas:approve3` | **纪委审批** | 待纪委审批 | 可以看到所有部门的申请 |
| **Level 4** | `"4"` | `rs:overseas:approve4` | **学院分管领导审批** | 待学院分管领导审批 | 可以看到所有部门的申请 |
| **Level 5** | `"5"` | `rs:overseas:approve5` | **学院党委书记审批** | 待学院党委书记审批 | 可以看到所有部门的申请 |
---
## 🎯 审批流程完整流转
### 流程图
```
申请提交
↓
┌─────────────────────────────────┐
│ Level 1: 部门负责人审批 │
│ - 审批人:各部门负责人 │
│ - 权限:rs:overseas:approve1 │
│ - 范围:仅本部门申请 │
└─────────────────────────────────┘
↓ (审批通过)
┌─────────────────────────────────┐
│ Level 2: 人事处审批 │
│ - 审批人:人事处工作人员 │
│ - 权限:rs:overseas:approve2 │
│ - 范围:所有部门申请 │
└─────────────────────────────────┘
↓ (审批通过)
┌─────────────────────────────────┐
│ Level 3: 纪委审批 │
│ - 审批人:纪委工作人员 │
│ - 权限:rs:overseas:approve3 │
│ - 范围:所有部门申请 │
└─────────────────────────────────┘
↓ (审批通过)
┌─────────────────────────────────┐
│ Level 4: 学院分管领导审批 │
│ - 审批人:学院分管领导 │
│ - 权限:rs:overseas:approve4 │
│ - 范围:所有部门申请 │
└─────────────────────────────────┘
↓ (审批通过)
┌─────────────────────────────────┐
│ Level 5: 学院党委书记审批 │
│ - 审批人:学院党委书记 │
│ - 权限:rs:overseas:approve5 │
│ - 范围:所有部门申请 │
└─────────────────────────────────┘
↓ (审批通过)
申请完成 → 生成出国记录
```
---
## 📝 各字段的具体命名标准
### 1. **当前审批环节**(approval_level字段)
**数据库字段**:`approval_level VARCHAR(10)`
**标准命名**(用于显示):
```java
switch (approvalLevel) {
case "1": return "部门负责人审批";
case "2": return "人事处审批";
case "3": return "纪委审批";
case "4": return "学院分管领导审批";
case "5": return "学院党委书记审批";
default: return "未知";
}
```
### 2. **当前审批人**(current_approver字段)
**数据库字段**:`current_approver VARCHAR(100)`
**标准命名格式**:
```java
switch (approvalLevel) {
case "1": return "待部门负责人审批";
case "2": return "待人事处审批";
case "3": return "待纪委审批";
case "4": return "待学院分管领导审批";
case "5": return "待学院党委书记审批";
default: return "待审批";
}
```
**特殊情况**:
- 如果是部门负责人级别,可能显示:`"待指定部门负责人"` 或 `"待{具体审批人姓名}审批"`
- 其他级别通常显示:`"待{审批环节}审批"`
### 3. **审批进度**(用于进度查询)
**显示格式**:`"已审批级别数/总级别数"`
**示例**:
- `"0/5"` - 未开始审批
- `"1/5"` - 部门负责人已审批
- `"2/5"` - 人事处已审批
- `"3/5"` - 纪委已审批
- `"4/5"` - 学院分管领导已审批
- `"5/5"` - 全部审批完成
---
## 🔧 数据库定义
### rs_overseas_application表字段
```sql
-- 审批级别字段
`approval_level` varchar(10) DEFAULT '1'
COMMENT '当前审批级别:1-部门负责人,2-人事处,3-纪委,4-学院分管领导,5-学院党委书记'
-- 当前审批人字段
`current_approver` varchar(100) DEFAULT NULL
COMMENT '当前审批人姓名或环节描述'
-- 当前审批人ID字段(冗余字段,仅供参考)
`current_approver_id` bigint DEFAULT NULL
COMMENT '当前审批人ID(冗余字段)'
```
---
## 🎨 前端显示示例
### 待审批列表显示
| 申请人 | 所属部门 | 当前审批环节 | 当前审批人 | 审批进度 |
|--------|---------|-------------|-----------|---------|
| 张三 | 教务处 | 部门负责人审批 | 待部门负责人审批 | 0% (0/5) |
| 李四 | 人事处 | 人事处审批 | 待人事处审批 | 20% (1/5) |
| 王五 | 学工处 | 纪委审批 | 待纪委审批 | 40% (2/5) |
| 赵六 | 后勤处 | 学院分管领导审批 | 待学院分管领导审批 | 60% (3/5) |
| 钱七 | 财务处 | 学院党委书记审批 | 待学院党委书记审批 | 80% (4/5) |
### 审批历史记录显示
```
📋 审批历史
✅ 第1级:部门负责人审批
审批人:张主任
审批时间:2025-10-27 10:30:25
审批结果:通过
审批意见:同意出国交流
✅ 第2级:人事处审批
审批人:李处长
审批时间:2025-10-27 14:20:15
审批结果:通过
审批意见:人事档案无问题,同意
⏳ 第3级:纪委审批
状态:待审批
⏸️ 第4级:学院分管领导审批
状态:未到审批阶段
⏸️ 第5级:学院党委书记审批
状态:未到审批阶段
```
---
## ⚠️ 当前存在的问题
### 问题1:命名不统一
**问题描述**:
- `RsOverseasApprovalMonitorServiceImpl.java` 中只定义了3个级别
- 与实际的5级审批流程不符
**影响范围**:
- 出国申请进度查询菜单的"当前审批环节"显示
- Level 4 和 Level 5 会显示为"未知"
**解决方案**:
需要修改 `getApprovalLevelText()` 方法,添加完整的5级定义
### 问题2:总审批级别数设置错误
**问题位置**:`RsOverseasApprovalMonitorServiceImpl.java` 第345-346行
```java
// 总审批级别数(假设最多3级:部门主管、分管领导、校领导)
vo.setTotalLevels(3);
```
**问题**:
- 实际是5级审批,但这里设置为3级
- 导致审批进度显示错误(如:2/3 而不是 2/5)
**影响**:
- 审批进度百分比计算错误
- 进度条显示不准确
---
## ✅ 建议的标准命名
### 统一的命名标准
| 级别 | 简称 | 完整名称 | 待审批显示 | 已审批显示 |
|------|------|---------|-----------|-----------|
| 1 | 部门负责人 | 部门负责人审批 | 待部门负责人审批 | 部门负责人已审批 |
| 2 | 人事处 | 人事处审批 | 待人事处审批 | 人事处已审批 |
| 3 | 纪委 | 纪委审批 | 待纪委审批 | 纪委已审批 |
| 4 | 学院分管领导 | 学院分管领导审批 | 待学院分管领导审批 | 学院分管领导已审批 |
| 5 | 学院党委书记 | 学院党委书记审批 | 待学院党委书记审批 | 学院党委书记已审批 |
### 代码实现建议
```java
/**
* 获取审批级别完整名称(用于"当前审批环节"列)
*/
private String getApprovalLevelText(String level) {
if (level == null) {
return "未开始";
}
switch (level) {
case "1":
return "部门负责人审批";
case "2":
return "人事处审批";
case "3":
return "纪委审批";
case "4":
return "学院分管领导审批";
case "5":
return "学院党委书记审批";
default:
return "未知";
}
}
/**
* 获取当前审批人显示文本(用于"当前审批人"列)
*/
private String getCurrentApproverText(String level) {
if (level == null) {
return "未指定";
}
switch (level) {
case "1":
return "待部门负责人审批";
case "2":
return "待人事处审批";
case "3":
return "待纪委审批";
case "4":
return "待学院分管领导审批";
case "5":
return "待学院党委书记审批";
default:
return "待审批";
}
}
```
---
## 📋 总结
### 当前审批级别定义
**完整的五级审批体系**:
1. **Level 1 - 部门负责人审批**
- 权限:`rs:overseas:approve1`
- 范围:仅本部门申请
- 显示:部门负责人审批
2. **Level 2 - 人事处审批**
- 权限:`rs:overseas:approve2`
- 范围:所有部门申请
- 显示:人事处审批
3. **Level 3 - 纪委审批**
- 权限:`rs:overseas:approve3`
- 范围:所有部门申请
- 显示:纪委审批
4. **Level 4 - 学院分管领导审批**
- 权限:`rs:overseas:approve4`
- 范围:所有部门申请
- 显示:学院分管领导审批
5. **Level 5 - 学院党委书记审批**
- 权限:`rs:overseas:approve5`
- 范围:所有部门申请
- 显示:学院党委书记审批
### 关键注意事项
- ✅ 系统使用**5级审批制度**
- ✅ 只有**部门负责人(Level 1)**需要部门过滤
- ✅ **Level 2-5** 可以看到所有部门的申请
- ⚠️ 出国申请进度查询模块存在命名不一致问题
- ⚠️ 总审批级别数应该是5而不是3
---
**分析完成时间**:2025-10-29
**分析人员**:Cursor AI
**文档版本**:v1.0
@@ -1,286 +0,0 @@
# 出国申请进度查询 - 审批级别修复说明
## 🔧 修复时间
2025-10-29
## 🎯 修复目标
将出国申请进度查询模块从3级审批改为完整的5级审批,与实际审批流程保持一致。
---
## 📝 修复内容
### 修复1:更新审批级别文本定义
**文件**:`RsOverseasApprovalMonitorServiceImpl.java`
**方法**:`getApprovalLevelText(String level)`
#### 修复前
```java
switch (level) {
case "1":
return "部门主管审批";
case "2":
return "分管领导审批";
case "3":
return "校领导审批";
default:
return "未知";
}
```
**问题**:
- ❌ Level 4 和 Level 5 显示为"未知"
- ❌ 命名与实际流程不符
#### 修复后
```java
/**
* 获取审批级别文本
* 五级审批制度:1-部门负责人,2-人事处,3-纪委,4-学院分管领导,5-学院党委书记
*/
switch (level) {
case "1":
return "部门负责人审批";
case "2":
return "人事处审批";
case "3":
return "纪委审批";
case "4":
return "学院分管领导审批";
case "5":
return "学院党委书记审批";
default:
return "未知";
}
```
**改进**:
- ✅ 支持完整的5级审批
- ✅ 命名与实际流程一致
- ✅ 添加了详细的注释说明
---
### 修复2:更新总审批级别数
**文件**:`RsOverseasApprovalMonitorServiceImpl.java`
**方法**:`fillApprovalProgress()`
#### 修复前
```java
// 总审批级别数(假设最多3级:部门主管、分管领导、校领导)
vo.setTotalLevels(3);
// 未开始审批
vo.setApprovedLevels(0);
vo.setTotalLevels(3);
vo.setApprovalProgress("0/3");
```
**问题**:
- ❌ 总级别数设置为3
- ❌ 审批进度显示错误(如:2/3 而不是 2/5)
- ❌ 进度百分比计算错误
#### 修复后
```java
// 总审批级别数(五级审批制度:部门负责人、人事处、纪委、学院分管领导、学院党委书记)
vo.setTotalLevels(5);
// 未开始审批
vo.setApprovedLevels(0);
vo.setTotalLevels(5);
vo.setApprovalProgress("0/5");
```
**改进**:
- ✅ 总级别数正确设置为5
- ✅ 审批进度显示正确
- ✅ 进度百分比计算准确
---
### 修复3:更新统计信息定义
**文件**:`RsOverseasApprovalMonitorServiceImpl.java`
**方法**:`getApprovalStatistics()`
#### 修复前
```java
// 2. 各审批级别的待审批数量
Map<String, Long> pendingByLevel = new HashMap<>();
pendingByLevel.put("level1", countByStatusAndLevel("0", "1", tenantId)); // 部门主管
pendingByLevel.put("level2", countByStatusAndLevel("0", "2", tenantId)); // 分管领导
pendingByLevel.put("level3", countByStatusAndLevel("0", "3", tenantId)); // 校领导
statistics.put("pendingByLevel", pendingByLevel);
```
**问题**:
- ❌ 只统计3个级别
- ❌ Level 4 和 Level 5 的统计缺失
#### 修复后
```java
// 2. 各审批级别的待审批数量(五级审批制度)
Map<String, Long> pendingByLevel = new HashMap<>();
pendingByLevel.put("level1", countByStatusAndLevel("0", "1", tenantId)); // 部门负责人
pendingByLevel.put("level2", countByStatusAndLevel("0", "2", tenantId)); // 人事处
pendingByLevel.put("level3", countByStatusAndLevel("0", "3", tenantId)); // 纪委
pendingByLevel.put("level4", countByStatusAndLevel("0", "4", tenantId)); // 学院分管领导
pendingByLevel.put("level5", countByStatusAndLevel("0", "5", tenantId)); // 学院党委书记
statistics.put("pendingByLevel", pendingByLevel);
```
**改进**:
- ✅ 统计所有5个级别
- ✅ 统计数据完整准确
- ✅ 命名规范一致
---
## 📊 修复效果对比
### 1. 当前审批环节显示
| 审批级别 | 修复前 | 修复后 | 状态 |
|---------|--------|--------|------|
| Level 1 | 部门主管审批 | 部门负责人审批 | ✅ 已修正 |
| Level 2 | 分管领导审批 | 人事处审批 | ✅ 已修正 |
| Level 3 | 校领导审批 | 纪委审批 | ✅ 已修正 |
| Level 4 | **未知** ❌ | 学院分管领导审批 | ✅ 已修复 |
| Level 5 | **未知** ❌ | 学院党委书记审批 | ✅ 已修复 |
### 2. 审批进度显示
| 已审批级别 | 修复前 | 修复后 | 进度百分比(修复前) | 进度百分比(修复后) |
|-----------|--------|--------|-------------------|-------------------|
| 0级 | 0/3 | 0/5 | 0% | 0% |
| 1级 | 1/3 | 1/5 | 33% ❌ | 20% ✅ |
| 2级 | 2/3 | 2/5 | 67% ❌ | 40% ✅ |
| 3级 | 3/3 | 3/5 | 100% ❌ | 60% ✅ |
| 4级 | - | 4/5 | - | 80% ✅ |
| 5级 | - | 5/5 | - | 100% ✅ |
### 3. 统计信息
| 统计项 | 修复前 | 修复后 |
|--------|--------|--------|
| 统计级别数 | 3个 | 5个 ✅ |
| Level 4统计 | ❌ 缺失 | ✅ 包含 |
| Level 5统计 | ❌ 缺失 | ✅ 包含 |
---
## 🎨 前端显示效果
### 待审批列表示例
#### 修复前
| 申请人 | 当前审批环节 | 审批进度 |
|--------|-------------|---------|
| 张三 | 部门主管审批 | 0/3 (0%) |
| 李四 | 分管领导审批 | 1/3 (33%) |
| 王五 | 校领导审批 | 2/3 (67%) |
| 赵六 | **未知** ❌ | 3/3 (100%) ❌ |
| 钱七 | **未知** ❌ | - |
#### 修复后
| 申请人 | 当前审批环节 | 审批进度 |
|--------|-------------|---------|
| 张三 | 部门负责人审批 | 0/5 (0%) |
| 李四 | 人事处审批 | 1/5 (20%) |
| 王五 | 纪委审批 | 2/5 (40%) |
| 赵六 | 学院分管领导审批 ✅ | 3/5 (60%) ✅ |
| 钱七 | 学院党委书记审批 ✅ | 4/5 (80%) ✅ |
---
## ✅ 修复验证
### 验证步骤
1. **重新编译后端**
```bash
cd backend
mvn clean package -pl ruoyi-admin -am -DskipTests
```
2. **重启后端服务**
```bash
./stop-main.sh
./start-main.sh
```
3. **登录系统测试**
- 访问"出国申请进度查询"菜单
- 查看待审批中和审批完成选项卡
- 验证以下内容:
#### 验证项1:当前审批环节显示
- [ ] Level 1 显示为"部门负责人审批"
- [ ] Level 2 显示为"人事处审批"
- [ ] Level 3 显示为"纪委审批"
- [ ] Level 4 显示为"学院分管领导审批"(不再是"未知")
- [ ] Level 5 显示为"学院党委书记审批"(不再是"未知")
#### 验证项2:审批进度显示
- [ ] 未审批显示:0/5 (0%)
- [ ] 部门负责人已审批:1/5 (20%)
- [ ] 人事处已审批:2/5 (40%)
- [ ] 纪委已审批:3/5 (60%)
- [ ] 学院分管领导已审批:4/5 (80%)
- [ ] 学院党委书记已审批:5/5 (100%)
#### 验证项3:统计信息
- [ ] 统计信息包含所有5个级别
- [ ] Level 4 和 Level 5 的统计数据正常显示
---
## 📋 修改文件清单
| 序号 | 文件 | 修改内容 | 行数 | 状态 |
|------|------|---------|------|------|
| 1 | `RsOverseasApprovalMonitorServiceImpl.java` | 更新审批级别文本定义 | 387-404 | ✅ |
| 2 | `RsOverseasApprovalMonitorServiceImpl.java` | 更新总审批级别数(已审批) | 345-346 | ✅ |
| 3 | `RsOverseasApprovalMonitorServiceImpl.java` | 更新总审批级别数(未审批) | 358-360 | ✅ |
| 4 | `RsOverseasApprovalMonitorServiceImpl.java` | 更新统计信息定义 | 152-157 | ✅ |
---
## 🎯 修复总结
### 修复前的问题
- ❌ 只支持3级审批,Level 4和5显示"未知"
- ❌ 总审批级别数错误(3而不是5)
- ❌ 审批进度百分比计算错误
- ❌ 统计信息不完整
### 修复后的改进
- ✅ 完整支持5级审批流程
- ✅ 所有审批级别正确显示
- ✅ 审批进度计算准确
- ✅ 统计信息完整
- ✅ 与实际业务流程一致
### 影响范围
- ✅ 仅修改出国申请进度查询模块
- ✅ 不影响其他模块
- ✅ 向后兼容
---
## 📞 相关文档
- 详细分析报告:`审批环节命名标准分析报告.md`
- 功能修改说明:`出国申请进度查询功能修改说明.md`
- 权限修改说明:`权限修改说明.md`
---
**修复状态**:✅ 已完成
**需要操作**:重新编译和重启后端服务
**版本**:v1.2
**最后更新**:2025-10-29
@@ -1,183 +0,0 @@
# 出国申请进度查询权限修改说明
## 📋 修改时间
2025-10-29
## 🎯 修改目的
将"出国申请进度查询"从"只能查看自己提交的申请"改为"拥有权限的用户可以查看所有申请"
---
## 📝 修改内容
### 1. 后端Service层修改
**文件**:`RsOverseasApprovalMonitorServiceImpl.java`
#### 修改前
```java
// 只查询当前用户提交的申请
Long currentUserId = LoginHelper.getUserId();
wrapper.eq("applicant_id", currentUserId);
```
#### 修改后
```java
// 不再限制申请人,拥有权限的用户可以看到所有数据
// 已删除 applicant_id 过滤条件
```
**影响方法**:
- ✅ `queryPendingList()` - 待审批列表
- ✅ `queryCompletedList()` - 已完成列表
---
### 2. 后端Controller层修改
**文件**:`RsOverseasApprovalMonitorController.java`
#### 修改点
- 更新注释说明:从"当前用户提交的"改为"所有"
- 更新日志输出:从"当前用户的"改为"所有"
---
### 3. 前端页面修改
**文件**:`monitor/index.vue`
#### 修改点
- 提示文字:从"这里显示您提交的"改为"这里显示所有"
---
## 🔍 权限控制
### 访问控制
- **权限标识**:`rs:overseas:list`
- **控制级别**:拥有此权限的用户可以查看所有数据
- **数据范围**:不再限制申请人,显示所有待审批和已完成的申请
### 查询逻辑
```sql
-- 待审批列表
SELECT * FROM rs_overseas_application
WHERE status = '0' -- 待审核状态
AND deleted = '0'
AND tenant_id = {租户ID}
ORDER BY apply_time DESC
-- 已完成列表
SELECT * FROM rs_overseas_application
WHERE status = '1' -- 已通过状态
AND deleted = '0'
AND tenant_id = {租户ID}
ORDER BY apply_time DESC
```
---
## ✅ 修改对比
| 项目 | 修改前 | 修改后 |
|------|--------|--------|
| 数据范围 | 当前用户提交的申请 | 所有申请 |
| 权限要求 | rs:overseas:list | rs:overseas:list(不变) |
| 查询条件 | applicant_id = 当前用户ID | 无申请人限制 |
| 用户体验 | 只能看到自己的 | 可以看到所有 |
---
## 🧪 测试验证
### 测试场景1:查看待审批列表
**测试步骤**:
1. 用户A登录系统(拥有 rs:overseas:list 权限)
2. 访问"出国申请进度查询"菜单
3. 切换到"待审批中"选项卡
**预期结果**:
- ✅ 能看到所有用户提交的待审批申请
- ✅ 包括用户A自己提交的和其他用户提交的
### 测试场景2:查看已完成列表
**测试步骤**:
1. 用户A登录系统(拥有 rs:overseas:list 权限)
2. 访问"出国申请进度查询"菜单
3. 切换到"审批完成"选项卡
**预期结果**:
- ✅ 能看到所有已完成审批的申请
- ✅ 不限制申请人
### 测试场景3:无权限用户
**测试步骤**:
1. 用户B登录系统(没有 rs:overseas:list 权限)
2. 尝试访问"出国申请进度查询"菜单
**预期结果**:
- ❌ 无法访问该菜单(权限控制在菜单级别)
---
## 📊 修改文件清单
| 序号 | 文件类型 | 文件路径 | 修改内容 | 状态 |
|------|---------|---------|---------|------|
| 1 | 后端Service | `RsOverseasApprovalMonitorServiceImpl.java` | 删除申请人ID过滤 | ✅ |
| 2 | 后端Controller | `RsOverseasApprovalMonitorController.java` | 更新注释和日志 | ✅ |
| 3 | 前端页面 | `monitor/index.vue` | 更新提示文字 | ✅ |
---
## ⚠️ 注意事项
### 1. 权限配置
- 确保只有需要查看所有数据的用户才有 `rs:overseas:list` 权限
- 如果需要区分不同权限级别,建议创建新的权限标识
### 2. 数据安全
- 所有查询仍然受租户ID限制
- 不会跨租户显示数据
### 3. 性能考虑
- 查询所有数据时,建议使用搜索条件过滤
- 分页显示可以减轻服务器压力
---
## 🚀 部署说明
### 后端部署
```bash
cd backend
mvn clean package -pl ruoyi-admin -am -DskipTests
./stop-main.sh
./start-main.sh
```
### 前端(如已运行无需重启)
```bash
# 前端代码修改后会自动热更新
# 如需打包部署:
cd web
npm run build
```
---
## 📞 相关文档
- 详细修改说明:`出国申请进度查询功能修改说明.md`
- 快速参考:`修改总结-快速参考.md`
---
**修改状态**:✅ 已完成
**生效方式**:需要重启后端服务
**版本**:v1.1