diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaProjectScheduleStepController.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaProjectScheduleStepController.java index 9f4f9a0..9cc13ed 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaProjectScheduleStepController.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaProjectScheduleStepController.java @@ -127,6 +127,15 @@ public class OaProjectScheduleStepController extends BaseController { return toAjax(iOaProjectScheduleStepService.batchDelay(batchDelayBo)); } + /** + * 批量设定步骤结束时间为同一时间 + */ + @RepeatSubmit() + @PutMapping("/batch-set-end") + public R batchSetEndTime(@RequestBody com.ruoyi.oa.domain.bo.BatchSetEndTimeBo bo) { + return toAjax(iOaProjectScheduleStepService.batchSetEndTime(bo)); + } + /** * 删除项目进度步骤跟踪 * diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaProjectScheduleStepMapper.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaProjectScheduleStepMapper.java index ef0403e..38e9fca 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaProjectScheduleStepMapper.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaProjectScheduleStepMapper.java @@ -63,4 +63,7 @@ public interface OaProjectScheduleStepMapper extends BaseMapperPlus trackIds, @Param("delayMinutes") Long delayMinutes); + + /** 批量设定 plan_end 为同一个具体时间 */ + int batchSetPlanEnd(@Param("trackIds") List trackIds, @Param("newEndTime") java.util.Date newEndTime); } diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaProjectScheduleStepService.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaProjectScheduleStepService.java index 40a14f0..2c8ecb9 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaProjectScheduleStepService.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaProjectScheduleStepService.java @@ -89,4 +89,7 @@ public interface IOaProjectScheduleStepService{ * @return 是否成功 */ Boolean batchDelay(com.ruoyi.oa.domain.bo.BatchDelayBo bo); + + /** 批量设定步骤结束时间为同一时间 */ + Boolean batchSetEndTime(com.ruoyi.oa.domain.bo.BatchSetEndTimeBo bo); } diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleStepServiceImpl.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleStepServiceImpl.java index 5100633..4d5f233 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleStepServiceImpl.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleStepServiceImpl.java @@ -1071,5 +1071,24 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS return updated > 0; } + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean batchSetEndTime(com.ruoyi.oa.domain.bo.BatchSetEndTimeBo bo) { + if (bo.getTrackIds() == null || bo.getTrackIds().isEmpty()) return false; + if (bo.getNewEndTime() == null) return false; + List steps = baseMapper.selectList( + new LambdaQueryWrapper() + .in(OaProjectScheduleStep::getTrackId, bo.getTrackIds()) + ); + String completedTrack = steps.stream() + .filter(s -> s.getStatus() != null && s.getStatus() == 2) + .map(OaProjectScheduleStep::getSecondLevelNode) + .collect(Collectors.joining(",")); + if (!completedTrack.isEmpty()) { + throw new RuntimeException("以下步骤已完成,不允许修改:" + completedTrack); + } + int updated = baseMapper.batchSetPlanEnd(bo.getTrackIds(), bo.getNewEndTime()); + return updated > 0; + } } diff --git a/ruoyi-oa/src/main/resources/mapper/oa/OaProjectScheduleStepMapper.xml b/ruoyi-oa/src/main/resources/mapper/oa/OaProjectScheduleStepMapper.xml index 5c34a75..7050949 100644 --- a/ruoyi-oa/src/main/resources/mapper/oa/OaProjectScheduleStepMapper.xml +++ b/ruoyi-oa/src/main/resources/mapper/oa/OaProjectScheduleStepMapper.xml @@ -321,4 +321,16 @@ AND status IN (0, 1) + + UPDATE oa_project_schedule_step + SET plan_end = #{newEndTime} + WHERE track_id IN + + #{id} + + AND del_flag = '0' + AND use_flag = '1' + AND status IN (0, 1) + + diff --git a/ruoyi-ui/src/api/oa/projectScheduleStep.js b/ruoyi-ui/src/api/oa/projectScheduleStep.js index e6376da..8f156e1 100644 --- a/ruoyi-ui/src/api/oa/projectScheduleStep.js +++ b/ruoyi-ui/src/api/oa/projectScheduleStep.js @@ -76,3 +76,12 @@ export function batchDelayStep (data) { data: data }) } + +// 批量设定步骤结束时间 +export function batchSetEndTimeStep (data) { + return request({ + url: '/oa/projectScheduleStep/batch-set-end', + method: 'put', + data: data + }) +} diff --git a/ruoyi-ui/src/layout/components/OverdueGuard.vue b/ruoyi-ui/src/layout/components/OverdueGuard.vue index 9443d4f..222ebbe 100644 --- a/ruoyi-ui/src/layout/components/OverdueGuard.vue +++ b/ruoyi-ui/src/layout/components/OverdueGuard.vue @@ -73,12 +73,14 @@ - + -
+
我的超期 ({{ pending.length }}) + +
+ + + +
+ + {{ pending.length }} + 超期 +
+
@@ -112,6 +123,7 @@ export default { return { visible: false, floatVisible: false, + floatMinimized: false, pending: [], totalCount: 0, doneCount: 0, @@ -137,7 +149,12 @@ export default { try { const res = await listMyOverdue() const list = res.data || [] - if (!list.length) return + if (!list.length) { + this.pending = [] + this.visible = false + this.floatVisible = false + return + } this.pending = list this.totalCount = list.length this.doneCount = 0 @@ -146,6 +163,19 @@ export default { this.floatVisible = false } catch (e) { /* ignore */ } }, + async refresh() { + // 手动 / 自动刷新:与服务端对齐,剔除已被别处操作完成的事项 + try { + const res = await listMyOverdue() + const list = res.data || [] + this.pending = list + if (!list.length) { + this.visible = false + this.floatVisible = false + this.floatMinimized = false + } + } catch (e) { /* ignore */ } + }, resetForm() { this.form = { action: '', newDeadline: '', reason: '' } }, @@ -163,6 +193,7 @@ export default { this.resetForm() this.visible = true this.floatVisible = false + this.floatMinimized = false }, async submit() { if (!this.current) return @@ -272,19 +303,11 @@ export default { if (this.strikeMap[key]) return try { await postponeComplete(item.business_type, item.business_id) - // 视觉划掉,1 秒后从列表移除 + // 视觉划掉,1 秒后从列表移除 + 跟服务端对齐 this.$set(this.strikeMap, key, true) - setTimeout(() => { - const idx = this.pending.findIndex(x => this.itemKey(x) === key) - if (idx >= 0) { - this.pending.splice(idx, 1) - this.doneCount++ - } + setTimeout(async () => { this.$delete(this.strikeMap, key) - if (!this.pending.length) { - this.visible = false - this.floatVisible = false - } + await this.refresh() }, 900) } catch (e) { this.$modal.msgError('完成失败,请稍后再试') @@ -330,6 +353,32 @@ export default { display: flex; align-items: center; gap: 6px; border-bottom: 1px solid #fde2e2; } .overdue-float .float-head i { font-size: 18px; } +.overdue-float .float-head .float-action { + font-size: 16px; cursor: pointer; color: #f56c6c; + padding: 4px; border-radius: 4px; transition: background .15s; +} +.overdue-float .float-head .float-action:first-of-type { margin-left: auto; } +.overdue-float .float-head .float-action:hover { background: rgba(245,108,108,0.15); } + +/* 最小化后的气泡 */ +.overdue-pill { + position: fixed; right: 16px; bottom: 16px; z-index: 2000; + background: #f56c6c; color: #fff; + border-radius: 20px; padding: 8px 14px; + display: flex; align-items: center; gap: 6px; + cursor: pointer; font-size: 13px; font-weight: 600; + box-shadow: 0 4px 16px rgba(245,108,108,.4); + animation: overduePillPulse 2.4s ease-in-out infinite; + transition: transform .15s; +} +.overdue-pill:hover { transform: scale(1.05); } +.overdue-pill i { font-size: 16px; } +.overdue-pill .num { font-size: 16px; font-weight: 800; } +.overdue-pill .lbl { opacity: 0.9; } +@keyframes overduePillPulse { + 0%, 100% { box-shadow: 0 4px 16px rgba(245,108,108,.4); } + 50% { box-shadow: 0 6px 24px rgba(245,108,108,.7); } +} .overdue-float .float-list { padding: 4px 0; overflow-y: auto; flex: 1; } .overdue-float .float-item { padding: 8px 14px; cursor: pointer; border-left: 3px solid transparent; diff --git a/ruoyi-ui/src/views/oa/project/pace/components/StepTable.vue b/ruoyi-ui/src/views/oa/project/pace/components/StepTable.vue index ca5c464..4e6c839 100644 --- a/ruoyi-ui/src/views/oa/project/pace/components/StepTable.vue +++ b/ruoyi-ui/src/views/oa/project/pace/components/StepTable.vue @@ -4,6 +4,8 @@ 新增 批量延期 + 批量设定时间
+ + + + + + + + + {{ selectedRows.length }} 行 + + +
+ 取消 + 确认设定 +
+
+ @@ -246,7 +269,7 @@