添加了浮窗最小话以及一键写入日期
This commit is contained in:
@@ -127,6 +127,15 @@ public class OaProjectScheduleStepController extends BaseController {
|
||||
return toAjax(iOaProjectScheduleStepService.batchDelay(batchDelayBo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设定步骤结束时间为同一时间
|
||||
*/
|
||||
@RepeatSubmit()
|
||||
@PutMapping("/batch-set-end")
|
||||
public R<Void> batchSetEndTime(@RequestBody com.ruoyi.oa.domain.bo.BatchSetEndTimeBo bo) {
|
||||
return toAjax(iOaProjectScheduleStepService.batchSetEndTime(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除项目进度步骤跟踪
|
||||
*
|
||||
|
||||
@@ -63,4 +63,7 @@ public interface OaProjectScheduleStepMapper extends BaseMapperPlus<OaProjectSch
|
||||
* @return 更新记录数
|
||||
*/
|
||||
int batchDelayPlanEnd(@Param("trackIds") List<Long> trackIds, @Param("delayMinutes") Long delayMinutes);
|
||||
|
||||
/** 批量设定 plan_end 为同一个具体时间 */
|
||||
int batchSetPlanEnd(@Param("trackIds") List<Long> trackIds, @Param("newEndTime") java.util.Date newEndTime);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<OaProjectScheduleStep> steps = baseMapper.selectList(
|
||||
new LambdaQueryWrapper<OaProjectScheduleStep>()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,4 +321,16 @@
|
||||
AND status IN (0, 1)
|
||||
</update>
|
||||
|
||||
<update id="batchSetPlanEnd">
|
||||
UPDATE oa_project_schedule_step
|
||||
SET plan_end = #{newEndTime}
|
||||
WHERE track_id IN
|
||||
<foreach collection="trackIds" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
AND del_flag = '0'
|
||||
AND use_flag = '1'
|
||||
AND status IN (0, 1)
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -73,12 +73,14 @@
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ============ 关不掉的浮窗 ============ -->
|
||||
<!-- ============ 浮窗(可最小化但不能关闭) ============ -->
|
||||
<transition name="float-slide">
|
||||
<div v-if="floatVisible" class="overdue-float">
|
||||
<div v-if="floatVisible && !floatMinimized" class="overdue-float">
|
||||
<div class="float-head">
|
||||
<i class="el-icon-warning"></i>
|
||||
<span>我的超期 ({{ pending.length }})</span>
|
||||
<i class="el-icon-refresh-right float-action" title="刷新" @click.stop="refresh"></i>
|
||||
<i class="el-icon-minus float-action" title="最小化" @click.stop="floatMinimized = true"></i>
|
||||
</div>
|
||||
<div class="float-list">
|
||||
<div v-for="item in pending" :key="item.business_type + '-' + item.business_id"
|
||||
@@ -100,6 +102,15 @@
|
||||
<div class="float-footer">点标题打开处理窗 · 点"完成"直接标记完成</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<!-- 最小化后的气泡 -->
|
||||
<transition name="float-slide">
|
||||
<div v-if="floatVisible && floatMinimized" class="overdue-pill" @click="floatMinimized = false">
|
||||
<i class="el-icon-warning"></i>
|
||||
<span class="num">{{ pending.length }}</span>
|
||||
<span class="lbl">超期</span>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<el-button type="text" icon="el-icon-plus" @click="addInnerData">新增</el-button>
|
||||
<el-button type="text" icon="el-icon-time" style="color:#e6a23c"
|
||||
@click="handleBatchDelay" :disabled="selectedRows.length === 0">批量延期</el-button>
|
||||
<el-button type="text" icon="el-icon-date" style="color:#409EFF"
|
||||
@click="handleBatchSetTime" :disabled="selectedRows.length === 0">批量设定时间</el-button>
|
||||
<slot name="extra-buttons"></slot>
|
||||
</div>
|
||||
<vxe-table size="mini" :height="tableHeight" ref="tableRef" border show-overflow :edit-config="editConfig" :data="innerData"
|
||||
@@ -183,6 +185,27 @@
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 批量设定时间对话框 -->
|
||||
<el-dialog :visible.sync="dialogBatchSetTimeVisible" title="批量设定时间" append-to-body width="420px">
|
||||
<el-alert type="info" :closable="false" show-icon
|
||||
title="把所选步骤的结束时间统一设为下面这个时间。已完成的步骤会被跳过。"
|
||||
style="margin-bottom: 14px;" />
|
||||
<el-form :model="dialogBatchSetTimeForm" label-width="100px">
|
||||
<el-form-item label="新结束时间" prop="newEndTime">
|
||||
<el-date-picker v-model="dialogBatchSetTimeForm.newEndTime"
|
||||
type="datetime" value-format="yyyy-MM-dd HH:mm:ss"
|
||||
placeholder="选择新的结束时间" style="width: 100%;" />
|
||||
</el-form-item>
|
||||
<el-form-item label="影响行数">
|
||||
<span style="color:#606266">{{ selectedRows.length }} 行</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer">
|
||||
<el-button @click="dialogBatchSetTimeVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="buttonLoading" @click="submitBatchSetTime">确认设定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :visible.sync="addDialogVisible" title="新增进度" append-to-body>
|
||||
<el-form :model="dialogAddForm" ref="formRef" label-width="120px">
|
||||
<el-form-item label="进度类别" prop="tabNode">
|
||||
@@ -246,7 +269,7 @@
|
||||
<script>
|
||||
import { addFileOperationRecord } from '@/api/oa/fileOperationRecord';
|
||||
import { applyProjectScheduleDelay } from "@/api/oa/projectScheduleDelay";
|
||||
import { updateProjectScheduleStep, batchDelayStep } from "@/api/oa/projectScheduleStep";
|
||||
import { updateProjectScheduleStep, batchDelayStep, batchSetEndTimeStep } from "@/api/oa/projectScheduleStep";
|
||||
import { listSupplier } from "@/api/oa/supplier";
|
||||
import { listUser } from "@/api/system/user";
|
||||
|
||||
@@ -298,6 +321,9 @@ export default {
|
||||
delayTo: '',
|
||||
applyReason: '',
|
||||
},
|
||||
// 批量设定时间对话框
|
||||
dialogBatchSetTimeVisible: false,
|
||||
dialogBatchSetTimeForm: { newEndTime: '' },
|
||||
// 批量延期对话框控制
|
||||
dialogBatchDelayVisible: false,
|
||||
dialogBatchDelayForm: {
|
||||
@@ -741,6 +767,38 @@ export default {
|
||||
};
|
||||
this.dialogBatchDelayVisible = true;
|
||||
},
|
||||
handleBatchSetTime () {
|
||||
if (this.selectedRows.length === 0) {
|
||||
this.$modal.msgWarning("请先选择要设定时间的步骤");
|
||||
return;
|
||||
}
|
||||
this.dialogBatchSetTimeForm = { newEndTime: '' };
|
||||
this.dialogBatchSetTimeVisible = true;
|
||||
},
|
||||
submitBatchSetTime () {
|
||||
if (!this.dialogBatchSetTimeForm.newEndTime) {
|
||||
this.$modal.msgWarning("请选择新的结束时间");
|
||||
return;
|
||||
}
|
||||
const trackIds = this.selectedRows.map(row => row.trackId).filter(id => id);
|
||||
if (trackIds.length === 0) {
|
||||
this.$modal.msgWarning("请选择有效的步骤");
|
||||
return;
|
||||
}
|
||||
this.buttonLoading = true;
|
||||
batchSetEndTimeStep({
|
||||
trackIds,
|
||||
newEndTime: this.dialogBatchSetTimeForm.newEndTime
|
||||
}).then(() => {
|
||||
this.$modal.msgSuccess("批量设定时间成功");
|
||||
this.dialogBatchSetTimeVisible = false;
|
||||
this.$emit("refresh", this.innerData);
|
||||
}).catch(() => {
|
||||
this.$modal.msgError("批量设定时间失败");
|
||||
}).finally(() => {
|
||||
this.buttonLoading = false;
|
||||
});
|
||||
},
|
||||
submitBatchDelay () {
|
||||
const trackIds = this.selectedRows.map(row => row.trackId).filter(id => id);
|
||||
if (trackIds.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user