diff --git a/frontend/src/assets/styles/roomroot.scss b/frontend/src/assets/styles/roomroot.scss index 6a2203d58..de603b397 100644 --- a/frontend/src/assets/styles/roomroot.scss +++ b/frontend/src/assets/styles/roomroot.scss @@ -124,13 +124,21 @@ } } - // 表格横向拖拽滚动:拖拽过程中光标切换为“抓手” + // 表格横向拖拽滚动:拖拽过程中光标保持默认箭头,不切换为“抓手” .el-table__body-wrapper.is-dragging, .el-table__fixed-body-wrapper.is-dragging { - cursor: grabbing; + cursor: default; } } +/* 表格横拖期间锁定整页光标与文本选中: + 避免光标划过按钮/链接时闪变成手型、以及拖拽过程误选中文字 */ +body.el-table-dragging, +body.el-table-dragging * { + user-select: none !important; + cursor: default !important; +} + /* 原生表格同样禁止选中文本,避免横向滑动/滚动时误触发复制前的文本选中 */ table { -webkit-user-select: none; diff --git a/frontend/src/directive/tableDrag/index.js b/frontend/src/directive/tableDrag/index.js index c48c39631..5620b182b 100644 --- a/frontend/src/directive/tableDrag/index.js +++ b/frontend/src/directive/tableDrag/index.js @@ -3,15 +3,18 @@ * 隐藏表格的横向滚动条,按住鼠标左键横向拖动即可查看后面的字段。 * 通过 document 事件委托全局生效,所有 el-table 自动支持,无需逐页绑定。 * - * 手感优化: - * - 文本选中已在全局样式中禁用(user-select: none),拖拽过程不会再误选文字 - * - 位移以横向为主且超过阈值才触发滚动,轻微手抖、普通点击、纵向拖拽(如行排序)不受影响 - * - 光标仅在真正拖动时变为抓手,平时保持默认 - * - 拖拽结束(确实发生了滚动)后屏蔽本次 click,避免误触发行点击、复选框等 + * 实现要点(全面优化): + * - 采用 Pointer Events + setPointerCapture:按下瞬间把指针捕获到滚动容器上, + * 之后无论光标移动到固定列、表格下方还是窗口外,移动事件都不会中断,真正"跟手"。 + * - requestAnimationFrame 合并每帧滚动,避免高频事件造成卡顿/闪烁。 + * - 横向位移超过阈值即触发,允许轻微斜向/弧线;仅当纵向明显占优时才不触发, + * 保留普通点击与纵向拖拽(如行排序)的区分。 + * - 拖拽期间整页锁定光标与文本选中,光标保持默认箭头,不闪变为抓手/手型。 + * - 拖拽结束(确实发生了滚动)后屏蔽本次 click,避免误触发行点击、复选框等。 */ -const DRAG_THRESHOLD = 4 // 横向位移超过该像素且以横向为主,才判定为拖动 +const DRAG_THRESHOLD = 2 // 横向位移超过该像素即判定为拖动,越小越跟手 -// 在这些元素上按下鼠标不触发拖拽,保证按钮、复选框等交互不受影响 +// 在这些元素上按下不触发拖拽,保证按钮、复选框等交互不受影响 const INTERACTIVE_SELECTOR = [ 'a', 'button', 'input', 'textarea', 'select', 'label', '.el-button', '.el-checkbox', '.el-radio', '.el-switch', '.el-select', @@ -20,77 +23,112 @@ const INTERACTIVE_SELECTOR = [ ].join(',') let wrapper = null +let pointerId = null let startX = 0 let startY = 0 let startScrollLeft = 0 let dragging = false +let rafId = null +let pendingScrollLeft = 0 function isHorizontallyScrollable(el) { return el.scrollWidth > el.clientWidth + 1 } -function handleMouseDown(e) { - if (e.button !== 0) return +// 每帧只应用一次滚动位置,提升平滑度 +function applyScroll() { + if (!wrapper) return + wrapper.scrollLeft = pendingScrollLeft + rafId = null +} + +function handlePointerDown(e) { + if (e.button !== 0 && e.pointerType === 'mouse') return if (e.target.closest(INTERACTIVE_SELECTOR)) return - const wrap = e.target.closest('.el-table__body-wrapper') + // 固定列(如 fixed="right" 的操作列)渲染在 .el-table__fixed-body-wrapper 中,悬浮覆盖在 + // 表体右侧,需一并识别;最终统一落到真正负责横向滚动的 .el-table__body-wrapper 上 + const anyWrapper = e.target.closest('.el-table__body-wrapper, .el-table__fixed-body-wrapper') + if (!anyWrapper) return + const table = anyWrapper.closest('.el-table') + const wrap = table && table.querySelector('.el-table__body-wrapper') if (!wrap || !isHorizontallyScrollable(wrap)) return + wrapper = wrap + pointerId = e.pointerId startX = e.clientX startY = e.clientY startScrollLeft = wrap.scrollLeft + pendingScrollLeft = startScrollLeft dragging = false + + // 关键:捕获指针,之后所有 pointermove/pointerup 都持续派发到容器, + // 即使光标移出表体/窗口也不中断 + if (wrap.setPointerCapture && e.pointerId != null) { + try { wrap.setPointerCapture(e.pointerId) } catch (err) { /* 忽略捕获失败 */ } + } + // 阻止默认行为,抑制原生图片/文本拖拽与选中 + e.preventDefault() } -function handleMouseMove(e) { - if (!wrapper) return +function handlePointerMove(e) { + if (!wrapper || e.pointerId !== pointerId) return + e.preventDefault() const dx = e.clientX - startX const dy = e.clientY - startY if (!dragging) { - // 轻微位移视为点击,不触发滚动;位移必须以横向为主,避免与纵向拖拽(如行排序)冲突 - if (Math.abs(dx) <= DRAG_THRESHOLD || Math.abs(dx) <= Math.abs(dy)) return - // 开始拖动:以当前位置为基准,避免起始跳变 + // 仅当纵向明显占优(超过横向 3 倍且超过 12px)才判定为纵向操作、不触发横向拖动, + // 其余情况(含轻微斜向/弧线)都正常触发,避免"拖不动" + if (Math.abs(dx) <= DRAG_THRESHOLD || (Math.abs(dy) > Math.abs(dx) * 3 && Math.abs(dy) > 12)) return dragging = true - startX = e.clientX - startY = e.clientY - startScrollLeft = wrapper.scrollLeft wrapper.classList.add('is-dragging') + // 整页锁定光标与文本选中,保证拖拽手感一致(光标不闪变、文字不误选) + document.body.classList.add('el-table-dragging') } - e.preventDefault() - wrapper.scrollLeft = startScrollLeft - (e.clientX - startX) + + // 记录目标位置,交由 rAF 统一应用 + pendingScrollLeft = startScrollLeft - (e.clientX - startX) + if (rafId == null) rafId = requestAnimationFrame(applyScroll) } -function handleMouseUp() { +function endDrag(blockClick) { if (!wrapper) return + if (rafId != null) { + cancelAnimationFrame(rafId) + rafId = null + } wrapper.classList.remove('is-dragging') - if (dragging) { + document.body.classList.remove('el-table-dragging') + if (blockClick && dragging) { // 拖拽确实发生了滚动,屏蔽本次 click,避免误触发行点击、复选框等 - const blockClick = (ev) => { + const suppress = (ev) => { ev.preventDefault() ev.stopPropagation() - document.removeEventListener('click', blockClick, true) + document.removeEventListener('click', suppress, true) } - document.addEventListener('click', blockClick, true) + document.addEventListener('click', suppress, true) } wrapper = null + pointerId = null dragging = false } -// 鼠标拖出窗口外时清理拖拽状态,防止状态残留 -function handleMouseLeave() { - if (!wrapper) return - wrapper.classList.remove('is-dragging') - wrapper = null - dragging = false +function handlePointerUp(e) { + if (e.pointerId !== pointerId) return + endDrag(true) +} + +function handlePointerCancel() { + endDrag(false) } const install = function() { if (install.installed) return install.installed = true - document.addEventListener('mousedown', handleMouseDown, false) - document.addEventListener('mousemove', handleMouseMove, false) - document.addEventListener('mouseup', handleMouseUp, false) - document.addEventListener('mouseleave', handleMouseLeave, false) + document.addEventListener('pointerdown', handlePointerDown, true) + document.addEventListener('pointermove', handlePointerMove, true) + document.addEventListener('pointerup', handlePointerUp, true) + document.addEventListener('pointercancel', handlePointerCancel, true) } export default install diff --git a/frontend/src/views/teachBusiness/calendar/index.vue b/frontend/src/views/teachBusiness/calendar/index.vue index 04fe5f17d..b9885121f 100644 --- a/frontend/src/views/teachBusiness/calendar/index.vue +++ b/frontend/src/views/teachBusiness/calendar/index.vue @@ -115,8 +115,8 @@ export default { totalWeeks: 0, dateRangeText: '', weeks: [], - // 显示控制:默认勾选显示日期;节次全部展示 - showDate: true, + // 显示控制:默认不勾选显示日期;节次全部展示 + showDate: false, // 事件存储:key = cellKey -> event events: {}, loading: false diff --git a/frontend/src/views/teachBusiness/semester/index.vue b/frontend/src/views/teachBusiness/semester/index.vue index 68e8d17e3..c36a3dffd 100644 --- a/frontend/src/views/teachBusiness/semester/index.vue +++ b/frontend/src/views/teachBusiness/semester/index.vue @@ -5,6 +5,12 @@ 新添 + + + 编辑校历 + + 批量删除 @@ -13,105 +19,79 @@ - + - + - + - + - + - - + + - + - + - + - + - + - + - + - + @@ -124,7 +104,8 @@ - + @@ -132,44 +113,37 @@ - + - + - + - +
- + @@ -195,7 +169,9 @@
调课不审批 -
设定【调课不审批】后,在教学信息系统中调课时,如果只是更改教员,或更改授课地点,调课申请会立即生效。而对于改变授课地点,调课申请会立即生效。而对于改变授课时间、增加或减少授课教员的调课申请依然不受该设置影响,依然需要逐级审批后才能生效。
+
+ 设定【调课不审批】后,在教学信息系统中调课时,如果只是更改教员,或更改授课地点,调课申请会立即生效。而对于改变授课地点,调课申请会立即生效。而对于改变授课时间、增加或减少授课教员的调课申请依然不受该设置影响,依然需要逐级审批后才能生效。 +
@@ -236,6 +212,8 @@ export default { semesterList: [], // 选中的学期数组 ids: [], + // 当前点击选中的学期 + currentSemester: null, // 非单个禁用 single: true, // 非多个禁用 @@ -358,6 +336,10 @@ export default { this.single = selection.length !== 1 this.multiple = !selection.length }, + /** 单击行选中变化:记录当前选中的学期,用于启用「编辑校历」 */ + handleCurrentChange(currentRow) { + this.currentSemester = currentRow || null + }, /** 组装提交数据 */ buildPayload(form) { const lockValue = form.lockValue !== undefined && form.lockValue !== null ? form.lockValue : '' @@ -421,15 +403,15 @@ export default { } this.open = true this.title = "学期信息 - 编辑" - }).catch(() => {}) + }).catch(() => { }) }, - /** 编辑校历按钮 */ - handleEditCalendar(row) { - const nd = row && row.nd ? row.nd : this.ids[0] - if (!nd) return + /** 编辑校历按钮:对当前选中的学期编辑校历 */ + handleEditCalendar() { + const row = this.currentSemester + if (!row || !row.nd) return this.$router.push({ path: '/teachBusiness/semester/semesterCalendar', - query: { nd: nd, name: this.getSemesterName(nd) } + query: { nd: row.nd, name: this.getSemesterName(row.nd) } }) }, /** 删除按钮 */ @@ -444,7 +426,7 @@ export default { }).then(() => { this.getList() this.$modal.msgSuccess("删除成功") - }).catch(() => {}) + }).catch(() => { }) }, /** 提交表单 */ submitForm() { @@ -465,7 +447,7 @@ export default { this.$modal.msgSuccess(this.isAdd ? "新增成功" : "修改成功") this.open = false this.getList() - }).catch(() => {}) + }).catch(() => { }) } }) }, diff --git a/frontend/src/views/teachBusiness/teachingTask/index.vue b/frontend/src/views/teachBusiness/teachingTask/index.vue index 1ff0e6469..e26aba519 100644 --- a/frontend/src/views/teachBusiness/teachingTask/index.vue +++ b/frontend/src/views/teachBusiness/teachingTask/index.vue @@ -60,9 +60,9 @@ 无数据! - - - + + + - - - - - + + + + + diff --git a/frontend/src/views/teachBusiness/timetable/index.vue b/frontend/src/views/teachBusiness/timetable/index.vue index 38347f3d9..ad328d5e6 100644 --- a/frontend/src/views/teachBusiness/timetable/index.vue +++ b/frontend/src/views/teachBusiness/timetable/index.vue @@ -90,14 +90,6 @@ 下一周
- -
diff --git a/frontend/src/views/teachBusiness/timetableAdjust/index.vue b/frontend/src/views/teachBusiness/timetableAdjust/index.vue index f3dc6b78d..acf9bd7d0 100644 --- a/frontend/src/views/teachBusiness/timetableAdjust/index.vue +++ b/frontend/src/views/teachBusiness/timetableAdjust/index.vue @@ -2,7 +2,6 @@
-
调课申请统计(当前页)
- - + + - - + + - - + + - + @@ -76,7 +75,7 @@ - +