16 KiB
16 KiB
🚀 出国行程变更申请菜单 - 立即部署指南
📋 部署概览
当前完成度: 60%
下一步任务: 后端API开发
预计开发时间: 7小时
第一步:数据库部署(15分钟)
1. 执行数据表创建脚本
# 进入MySQL
mysql -u root -p
# 选择数据库
use hzdyjs_xg;
# 执行建表脚本
source /Users/weizzzz/Documents/Cursor/0828CURSOR/all-project/docs/设计文档/database/rs_overseas_itinerary_change.sql
验证表创建:
-- 查看表结构
DESC rs_overseas_itinerary_change;
-- 查看索引
SHOW INDEX FROM rs_overseas_itinerary_change;
2. 执行菜单权限配置脚本
⚠️ 注意:执行前需要确认人事管理菜单的实际menu_id!
-- 1. 查询人事管理菜单ID
SELECT menu_id, menu_name, parent_id FROM sys_menu WHERE menu_name = '人事管理';
如果parent_id不是105,需要修改SQL脚本:
-- 打开菜单脚本文件,全局替换parent_id
-- 将 parent_id = 105 替换为实际的ID
执行菜单配置:
source /Users/weizzzz/Documents/Cursor/0828CURSOR/all-project/docs/设计文档/database/rs_overseas_itinerary_change_menu.sql
验证菜单创建:
-- 查询变更行程菜单及其子菜单
SELECT
menu_id,
menu_name,
parent_id,
order_num,
path,
component,
perms,
menu_type,
visible,
status
FROM sys_menu
WHERE menu_id >= 2050 AND menu_id <= 2057
ORDER BY menu_id;
预期结果:应该看到8条菜单记录
3. 为管理员角色分配权限
-- 查询管理员角色ID(通常为1)
SELECT role_id, role_name FROM sys_role WHERE role_name LIKE '%管理员%';
-- 为管理员分配菜单权限(假设role_id=1)
INSERT INTO sys_role_menu VALUES (1, 2050); -- 变更行程菜单
INSERT INTO sys_role_menu VALUES (1, 2051); -- 变更行程管理页面
INSERT INTO sys_role_menu VALUES (1, 2052); -- 查询权限
INSERT INTO sys_role_menu VALUES (1, 2053); -- 新增权限
INSERT INTO sys_role_menu VALUES (1, 2054); -- 修改权限
INSERT INTO sys_role_menu VALUES (1, 2055); -- 删除权限
INSERT INTO sys_role_menu VALUES (1, 2056); -- 撤回权限
INSERT INTO sys_role_menu VALUES (1, 2057); -- 导出权限
-- 验证权限分配
SELECT rm.*, m.menu_name
FROM sys_role_menu rm
LEFT JOIN sys_menu m ON rm.menu_id = m.menu_id
WHERE rm.menu_id >= 2050 AND rm.menu_id <= 2057;
4. 清理浏览器缓存
前端菜单有缓存,需要清理:
- 退出系统登录
- 清除浏览器缓存(Ctrl+Shift+Delete)
- 重新登录
- 查看人事管理菜单下是否出现"变更行程"菜单
第二步:后端API开发(4小时)
1. 创建实体类(30分钟)
参考文件: backend/src/main/java/com/hzdyjs/modules/rs/domain/RsOverseasApplication.java
需要创建:
backend/src/main/java/com/hzdyjs/modules/rs/domain/
└── RsOverseasItineraryChange.java
关键点:
- ✅ 使用
@TableName("rs_overseas_itinerary_change") - ✅ 主键使用
@TableId(type = IdType.ASSIGN_ID)(雪花ID) - ✅ 逻辑删除字段使用
@TableLogic - ✅ 时间字段使用
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - ✅ 继承
BaseEntity(包含创建者、更新者等字段)
2. 创建VO/Form/Query类(30分钟)
需要创建:
backend/src/main/java/com/hzdyjs/modules/rs/domain/
├── vo/
│ └── ItineraryChangeVO.java # 返回对象
├── form/
│ └── ItineraryChangeForm.java # 表单对象
└── query/
└── ItineraryChangeQuery.java # 查询对象
参考API文档:docs/开发文档/API/变更行程管理API.md(第592-707行)
关键验证注解:
@NotNull- 非空校验@NotBlank- 非空字符串校验@Size(max = 500)- 长度校验@JsonFormat- 日期格式化
3. 创建Mapper层(30分钟)
需要创建:
backend/src/main/java/com/hzdyjs/modules/rs/mapper/
├── ItineraryChangeMapper.java
└── xml/
└── ItineraryChangeMapper.xml
Mapper接口:
@Mapper
public interface ItineraryChangeMapper extends BaseMapper<RsOverseasItineraryChange> {
// 自定义查询方法(如果需要)
List<ItineraryChangeVO> selectChangeList(ItineraryChangeQuery query);
}
Mapper XML:
- 配置resultMap
- 编写自定义SQL(如果需要)
- 多表关联查询(如果需要)
4. 创建Service层(1小时)
需要创建:
backend/src/main/java/com/hzdyjs/modules/rs/service/
├── IItineraryChangeService.java
└── impl/
└── ItineraryChangeServiceImpl.java
Service接口方法:
public interface IItineraryChangeService {
// 查询已通过的出国申请列表
List<OverseasApplicationSimpleVO> listApprovedApplications();
// 查询变更申请列表(分页)
TableDataInfo<ItineraryChangeVO> selectChangeList(ItineraryChangeQuery query);
// 查询变更申请详情
ItineraryChangeVO selectChangeById(Long changeId);
// 新增变更申请
Long insertChange(ItineraryChangeForm form);
// 修改并重新提交
int updateChange(Long changeId, ItineraryChangeForm form);
// 撤回变更申请
int cancelChange(Long changeId, String cancelReason);
// 删除变更申请
int deleteChangeById(Long changeId);
// 导出变更申请列表
List<ItineraryChangeVO> selectChangeList(ItineraryChangeQuery query);
}
Service实现关键逻辑:
新增变更申请:
@Override
public Long insertChange(ItineraryChangeForm form) {
// 1. 校验applicationId是否存在且状态为已通过
RsOverseasApplication application = applicationMapper.selectById(form.getApplicationId());
if (application == null || !"1".equals(application.getStatus())) {
throw new ServiceException("出国申请不存在或状态不是已通过");
}
// 2. 校验是否已有待审核的变更申请
LambdaQueryWrapper<RsOverseasItineraryChange> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(RsOverseasItineraryChange::getApplicationId, form.getApplicationId())
.eq(RsOverseasItineraryChange::getStatus, "0");
Long count = baseMapper.selectCount(wrapper);
if (count > 0) {
throw new ServiceException("该出国申请已存在待审核的变更申请,请先处理完毕");
}
// 3. 复制原申请信息到original_*字段
RsOverseasItineraryChange change = new RsOverseasItineraryChange();
change.setApplicationId(application.getApplicationId());
change.setOriginalDestination(application.getDestination());
change.setOriginalExitReason(application.getExitReason());
// ... 其他original字段
// 4. 填充变更信息
BeanUtils.copyProperties(form, change);
// 5. 计算新出国天数
if (form.getNewStartDate() != null && form.getNewEndDate() != null) {
long days = ChronoUnit.DAYS.between(
form.getNewStartDate().toInstant(),
form.getNewEndDate().toInstant()
) + 1;
change.setNewDuration((int) days);
}
// 6. 设置申请人信息(从当前登录用户获取)
LoginUser loginUser = SecurityUtils.getLoginUser();
change.setApplicantId(loginUser.getUserId());
change.setApplicantName(loginUser.getUsername());
change.setDepartment(loginUser.getDeptName());
// 7. 设置状态为待审核
change.setStatus("0");
// 8. 插入数据库
baseMapper.insert(change);
return change.getChangeId();
}
5. 创建Controller层(1小时)
需要创建:
backend/src/main/java/com/hzdyjs/modules/rs/controller/
└── ItineraryChangeController.java
Controller示例:
@RestController
@RequestMapping("/rs/itinerary-change")
public class ItineraryChangeController extends BaseController {
@Autowired
private IItineraryChangeService itineraryChangeService;
/**
* 查询已通过的出国申请列表
*/
@GetMapping("/approvedApplications")
@SaCheckPermission("rs:itineraryChange:add")
public R<List<OverseasApplicationSimpleVO>> listApprovedApplications() {
List<OverseasApplicationSimpleVO> list = itineraryChangeService.listApprovedApplications();
return R.ok(list);
}
/**
* 查询变更申请列表(分页)
*/
@GetMapping("/list")
@SaCheckPermission("rs:itineraryChange:list")
public TableDataInfo<ItineraryChangeVO> list(ItineraryChangeQuery query) {
startPage();
List<ItineraryChangeVO> list = itineraryChangeService.selectChangeList(query);
return getDataTable(list);
}
/**
* 查询变更申请详情
*/
@GetMapping("/{changeId}")
@SaCheckPermission("rs:itineraryChange:query")
public R<ItineraryChangeVO> getInfo(@PathVariable Long changeId) {
return R.ok(itineraryChangeService.selectChangeById(changeId));
}
/**
* 新增变更申请
*/
@PostMapping
@SaCheckPermission("rs:itineraryChange:add")
public R<Map<String, Object>> add(@Validated @RequestBody ItineraryChangeForm form) {
Long changeId = itineraryChangeService.insertChange(form);
Map<String, Object> result = new HashMap<>();
result.put("changeId", changeId);
return R.ok(result, "提交成功");
}
/**
* 修改并重新提交
*/
@PutMapping("/{changeId}")
@SaCheckPermission("rs:itineraryChange:edit")
public R<Void> edit(@PathVariable Long changeId, @Validated @RequestBody ItineraryChangeForm form) {
itineraryChangeService.updateChange(changeId, form);
return R.ok("修改成功");
}
/**
* 撤回变更申请
*/
@PutMapping("/cancel/{changeId}")
@SaCheckPermission("rs:itineraryChange:cancel")
public R<Void> cancel(@PathVariable Long changeId, @RequestParam String cancelReason) {
itineraryChangeService.cancelChange(changeId, cancelReason);
return R.ok("撤回成功");
}
/**
* 删除变更申请
*/
@DeleteMapping("/{changeId}")
@SaCheckPermission("rs:itineraryChange:remove")
public R<Void> remove(@PathVariable Long changeId) {
itineraryChangeService.deleteChangeById(changeId);
return R.ok("删除成功");
}
/**
* 导出变更申请列表
*/
@PostMapping("/export")
@SaCheckPermission("rs:itineraryChange:export")
public void export(ItineraryChangeQuery query, HttpServletResponse response) {
List<ItineraryChangeVO> list = itineraryChangeService.selectChangeList(query);
ExcelUtil.exportExcel(list, "变更行程列表", ItineraryChangeVO.class, response);
}
}
第三步:前端API对接(1小时)
1. 创建API文件
创建文件: web/src/api/rs/itinerary-change.ts
import request from '@/utils/request'
// 查询已通过的出国申请列表
export function listApprovedApplications() {
return request({
url: '/rs/itinerary-change/approvedApplications',
method: 'get'
})
}
// 查询变更申请列表
export function listItineraryChange(query: any) {
return request({
url: '/rs/itinerary-change/list',
method: 'get',
params: query
})
}
// 查询变更申请详情
export function getItineraryChange(changeId: number) {
return request({
url: `/rs/itinerary-change/${changeId}`,
method: 'get'
})
}
// 新增变更申请
export function addItineraryChange(data: any) {
return request({
url: '/rs/itinerary-change',
method: 'post',
data: data
})
}
// 修改并重新提交
export function updateItineraryChange(changeId: number, data: any) {
return request({
url: `/rs/itinerary-change/${changeId}`,
method: 'put',
data: data
})
}
// 撤回变更申请
export function cancelItineraryChange(changeId: number, cancelReason: string) {
return request({
url: `/rs/itinerary-change/cancel/${changeId}`,
method: 'put',
params: { cancelReason }
})
}
// 删除变更申请
export function delItineraryChange(changeId: number) {
return request({
url: `/rs/itinerary-change/${changeId}`,
method: 'delete'
})
}
// 导出变更申请列表
export function exportItineraryChange(query: any) {
return request({
url: '/rs/itinerary-change/export',
method: 'post',
data: query,
responseType: 'blob'
})
}
2. 修改前端页面导入API
在itinerary-change.vue文件顶部添加:
import {
listApprovedApplications,
listItineraryChange,
getItineraryChange,
addItineraryChange,
updateItineraryChange,
cancelItineraryChange,
delItineraryChange,
exportItineraryChange
} from '@/api/rs/itinerary-change'
然后替换所有的API调用。
第四步:功能测试(2小时)
1. 后端接口测试(1小时)
使用Postman或Apifox测试所有接口:
测试清单:
- GET /rs/itinerary-change/approvedApplications
- GET /rs/itinerary-change/list
- GET /rs/itinerary-change/{changeId}
- POST /rs/itinerary-change
- PUT /rs/itinerary-change/{changeId}
- PUT /rs/itinerary-change/cancel/{changeId}
- DELETE /rs/itinerary-change/{changeId}
- POST /rs/itinerary-change/export
测试要点:
- ✅ 权限验证(无权限返回403)
- ✅ 参数校验(必填参数缺失)
- ✅ 业务校验(状态校验、重复提交校验等)
- ✅ 数据正确性(返回数据格式正确)
2. 前后端联调测试(1小时)
测试流程:
-
新增变更申请:
- 打开变更行程页面
- 点击"新增变更申请"
- 选择已通过的出国申请
- 验证原申请信息自动填充
- 选择变更类型,填写变更信息
- 提交并验证成功
-
查询列表:
- 验证列表数据正确显示
- 测试搜索功能
- 测试分页功能
-
撤回申请:
- 找到待审核的申请
- 点击撤回按钮
- 输入撤回原因
- 验证状态变为"已撤回"
-
删除申请:
- 删除非已通过状态的申请
- 验证删除成功
-
导出功能:
- 点击导出按钮
- 验证Excel文件下载
- 检查导出数据正确性
开发时间估算
| 任务 | 预计时间 | 说明 |
|---|---|---|
| 数据库部署 | 0.5小时 | 执行SQL脚本、权限配置 |
| 后端API开发 | 4小时 | Entity/VO/Mapper/Service/Controller |
| 前端API对接 | 1小时 | 创建API文件、修改页面引用 |
| 功能测试 | 2小时 | 接口测试、联调测试 |
| 总计 | 7.5小时 | 约1个工作日 |
参考文档
- API文档:
docs/开发文档/API/变更行程管理API.md - 功能报告:
✅变更行程管理功能完成报告.md - 数据库表:
docs/设计文档/database/rs_overseas_itinerary_change.sql - 菜单配置:
docs/设计文档/database/rs_overseas_itinerary_change_menu.sql - 前端页面:
web/src/views/rs/overseas/itinerary-change.vue - 后端规范:
.cursor/cursorrules-backend.md - 前端规范:
.cursor/cursorrules-frontend.md
常见问题
Q1: 菜单不显示怎么办?
A:
- 检查菜单SQL是否执行成功
- 检查角色是否分配了菜单权限
- 清除浏览器缓存,重新登录
- 检查菜单的visible字段是否为'0'(显示)
Q2: 接口返回403权限不足?
A:
- 检查Controller方法是否添加了
@SaCheckPermission注解 - 检查权限标识是否正确(rs:itineraryChange:xxx)
- 检查用户角色是否分配了相应的菜单权限
Q3: 前端API调用失败?
A:
- 检查后端服务是否启动
- 检查接口地址是否正确
- 打开浏览器开发者工具,查看Network请求详情
- 检查请求参数格式是否正确
Q4: 新增申请时提示"已存在待审核的变更申请"?
A:
- 这是正常的业务校验
- 需要先处理完待审核的申请(撤回或等待审批)
- 然后才能提交新的变更申请
下一步建议
完成核心功能开发后,可以考虑以下增强功能:
- 审批流程集成 - 集成Warm-Flow工作流
- 消息通知 - 审批结果通知
- 数据统计 - 变更申请统计图表
- 移动端适配 - uni-app移动端页面
开发愉快! 🎉
如有问题,请参考API文档或联系开发团队。