添加了浮窗最小话以及一键写入日期
This commit is contained in:
@@ -127,6 +127,15 @@ public class OaProjectScheduleStepController extends BaseController {
|
|||||||
return toAjax(iOaProjectScheduleStepService.batchDelay(batchDelayBo));
|
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 更新记录数
|
* @return 更新记录数
|
||||||
*/
|
*/
|
||||||
int batchDelayPlanEnd(@Param("trackIds") List<Long> trackIds, @Param("delayMinutes") Long delayMinutes);
|
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 是否成功
|
* @return 是否成功
|
||||||
*/
|
*/
|
||||||
Boolean batchDelay(com.ruoyi.oa.domain.bo.BatchDelayBo bo);
|
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;
|
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)
|
AND status IN (0, 1)
|
||||||
</update>
|
</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>
|
</mapper>
|
||||||
|
|||||||
@@ -76,3 +76,12 @@ export function batchDelayStep (data) {
|
|||||||
data: data
|
data: data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量设定步骤结束时间
|
||||||
|
export function batchSetEndTimeStep (data) {
|
||||||
|
return request({
|
||||||
|
url: '/oa/projectScheduleStep/batch-set-end',
|
||||||
|
method: 'put',
|
||||||
|
data: data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,12 +73,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- ============ 关不掉的浮窗 ============ -->
|
<!-- ============ 浮窗(可最小化但不能关闭) ============ -->
|
||||||
<transition name="float-slide">
|
<transition name="float-slide">
|
||||||
<div v-if="floatVisible" class="overdue-float">
|
<div v-if="floatVisible && !floatMinimized" class="overdue-float">
|
||||||
<div class="float-head">
|
<div class="float-head">
|
||||||
<i class="el-icon-warning"></i>
|
<i class="el-icon-warning"></i>
|
||||||
<span>我的超期 ({{ pending.length }})</span>
|
<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>
|
||||||
<div class="float-list">
|
<div class="float-list">
|
||||||
<div v-for="item in pending" :key="item.business_type + '-' + item.business_id"
|
<div v-for="item in pending" :key="item.business_type + '-' + item.business_id"
|
||||||
@@ -100,6 +102,15 @@
|
|||||||
<div class="float-footer">点标题打开处理窗 · 点"完成"直接标记完成</div>
|
<div class="float-footer">点标题打开处理窗 · 点"完成"直接标记完成</div>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -112,6 +123,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
visible: false,
|
visible: false,
|
||||||
floatVisible: false,
|
floatVisible: false,
|
||||||
|
floatMinimized: false,
|
||||||
pending: [],
|
pending: [],
|
||||||
totalCount: 0,
|
totalCount: 0,
|
||||||
doneCount: 0,
|
doneCount: 0,
|
||||||
@@ -137,7 +149,12 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const res = await listMyOverdue()
|
const res = await listMyOverdue()
|
||||||
const list = res.data || []
|
const list = res.data || []
|
||||||
if (!list.length) return
|
if (!list.length) {
|
||||||
|
this.pending = []
|
||||||
|
this.visible = false
|
||||||
|
this.floatVisible = false
|
||||||
|
return
|
||||||
|
}
|
||||||
this.pending = list
|
this.pending = list
|
||||||
this.totalCount = list.length
|
this.totalCount = list.length
|
||||||
this.doneCount = 0
|
this.doneCount = 0
|
||||||
@@ -146,6 +163,19 @@ export default {
|
|||||||
this.floatVisible = false
|
this.floatVisible = false
|
||||||
} catch (e) { /* ignore */ }
|
} 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() {
|
resetForm() {
|
||||||
this.form = { action: '', newDeadline: '', reason: '' }
|
this.form = { action: '', newDeadline: '', reason: '' }
|
||||||
},
|
},
|
||||||
@@ -163,6 +193,7 @@ export default {
|
|||||||
this.resetForm()
|
this.resetForm()
|
||||||
this.visible = true
|
this.visible = true
|
||||||
this.floatVisible = false
|
this.floatVisible = false
|
||||||
|
this.floatMinimized = false
|
||||||
},
|
},
|
||||||
async submit() {
|
async submit() {
|
||||||
if (!this.current) return
|
if (!this.current) return
|
||||||
@@ -272,19 +303,11 @@ export default {
|
|||||||
if (this.strikeMap[key]) return
|
if (this.strikeMap[key]) return
|
||||||
try {
|
try {
|
||||||
await postponeComplete(item.business_type, item.business_id)
|
await postponeComplete(item.business_type, item.business_id)
|
||||||
// 视觉划掉,1 秒后从列表移除
|
// 视觉划掉,1 秒后从列表移除 + 跟服务端对齐
|
||||||
this.$set(this.strikeMap, key, true)
|
this.$set(this.strikeMap, key, true)
|
||||||
setTimeout(() => {
|
setTimeout(async () => {
|
||||||
const idx = this.pending.findIndex(x => this.itemKey(x) === key)
|
|
||||||
if (idx >= 0) {
|
|
||||||
this.pending.splice(idx, 1)
|
|
||||||
this.doneCount++
|
|
||||||
}
|
|
||||||
this.$delete(this.strikeMap, key)
|
this.$delete(this.strikeMap, key)
|
||||||
if (!this.pending.length) {
|
await this.refresh()
|
||||||
this.visible = false
|
|
||||||
this.floatVisible = false
|
|
||||||
}
|
|
||||||
}, 900)
|
}, 900)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.$modal.msgError('完成失败,请稍后再试')
|
this.$modal.msgError('完成失败,请稍后再试')
|
||||||
@@ -330,6 +353,32 @@ export default {
|
|||||||
display: flex; align-items: center; gap: 6px; border-bottom: 1px solid #fde2e2;
|
display: flex; align-items: center; gap: 6px; border-bottom: 1px solid #fde2e2;
|
||||||
}
|
}
|
||||||
.overdue-float .float-head i { font-size: 18px; }
|
.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-list { padding: 4px 0; overflow-y: auto; flex: 1; }
|
||||||
.overdue-float .float-item {
|
.overdue-float .float-item {
|
||||||
padding: 8px 14px; cursor: pointer; border-left: 3px solid transparent;
|
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-plus" @click="addInnerData">新增</el-button>
|
||||||
<el-button type="text" icon="el-icon-time" style="color:#e6a23c"
|
<el-button type="text" icon="el-icon-time" style="color:#e6a23c"
|
||||||
@click="handleBatchDelay" :disabled="selectedRows.length === 0">批量延期</el-button>
|
@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>
|
<slot name="extra-buttons"></slot>
|
||||||
</div>
|
</div>
|
||||||
<vxe-table size="mini" :height="tableHeight" ref="tableRef" border show-overflow :edit-config="editConfig" :data="innerData"
|
<vxe-table size="mini" :height="tableHeight" ref="tableRef" border show-overflow :edit-config="editConfig" :data="innerData"
|
||||||
@@ -183,6 +185,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</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-dialog :visible.sync="addDialogVisible" title="新增进度" append-to-body>
|
||||||
<el-form :model="dialogAddForm" ref="formRef" label-width="120px">
|
<el-form :model="dialogAddForm" ref="formRef" label-width="120px">
|
||||||
<el-form-item label="进度类别" prop="tabNode">
|
<el-form-item label="进度类别" prop="tabNode">
|
||||||
@@ -246,7 +269,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { addFileOperationRecord } from '@/api/oa/fileOperationRecord';
|
import { addFileOperationRecord } from '@/api/oa/fileOperationRecord';
|
||||||
import { applyProjectScheduleDelay } from "@/api/oa/projectScheduleDelay";
|
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 { listSupplier } from "@/api/oa/supplier";
|
||||||
import { listUser } from "@/api/system/user";
|
import { listUser } from "@/api/system/user";
|
||||||
|
|
||||||
@@ -298,6 +321,9 @@ export default {
|
|||||||
delayTo: '',
|
delayTo: '',
|
||||||
applyReason: '',
|
applyReason: '',
|
||||||
},
|
},
|
||||||
|
// 批量设定时间对话框
|
||||||
|
dialogBatchSetTimeVisible: false,
|
||||||
|
dialogBatchSetTimeForm: { newEndTime: '' },
|
||||||
// 批量延期对话框控制
|
// 批量延期对话框控制
|
||||||
dialogBatchDelayVisible: false,
|
dialogBatchDelayVisible: false,
|
||||||
dialogBatchDelayForm: {
|
dialogBatchDelayForm: {
|
||||||
@@ -741,6 +767,38 @@ export default {
|
|||||||
};
|
};
|
||||||
this.dialogBatchDelayVisible = true;
|
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 () {
|
submitBatchDelay () {
|
||||||
const trackIds = this.selectedRows.map(row => row.trackId).filter(id => id);
|
const trackIds = this.selectedRows.map(row => row.trackId).filter(id => id);
|
||||||
if (trackIds.length === 0) {
|
if (trackIds.length === 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user