feat: 列宽拖拽 + 筛选栏 + 截图导出 + UI优化
- 新增 v-column-resize 指令,11 张表格均支持拖拽调整列宽 - 工作计划/商机跟单/要客拜访新增筛选栏(客户经理 + 状态) - 汇总栏 chip 可点击筛选,支持重置与计数 - 新增截图导出功能(dom-to-image-more,像素级文字渲染) - 截图范围从标题开始,自动隐藏操作按钮,5px白边 - 截图时页面闪烁动画反馈 - 全局 CSS 完善(列宽手柄、截图模式、捕获动画) - 仪表盘 UI 细节优化 + 亮灯表标签调整 - 登录页 UI 改进 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* v-column-resize directive — adds drag-to-resize handles to el-table column headers.
|
||||
*
|
||||
* Usage: <el-table v-column-resize ...>
|
||||
*
|
||||
* Holders drag the right edge of any column header (except the last) to resize.
|
||||
* Works with Element Plus's fixed table-layout by updating <col> widths in
|
||||
* every colgroup (header + body tables stay in sync).
|
||||
*/
|
||||
|
||||
import type { Directive, DirectiveBinding } from 'vue'
|
||||
|
||||
interface DragState {
|
||||
startX: number
|
||||
startWidth: number
|
||||
th: HTMLElement
|
||||
tableWrapper: HTMLElement
|
||||
colIndex: number
|
||||
}
|
||||
|
||||
const drag: DragState = {
|
||||
startX: 0,
|
||||
startWidth: 0,
|
||||
th: null!,
|
||||
tableWrapper: null!,
|
||||
colIndex: -1,
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────
|
||||
|
||||
function getColIndex(th: HTMLElement): number {
|
||||
const cells = Array.from(th.parentElement?.children || [])
|
||||
return cells.indexOf(th)
|
||||
}
|
||||
|
||||
/** All <col> elements across every colgroup inside the wrapper, by col index */
|
||||
function allCols(wrapper: HTMLElement, idx: number): HTMLElement[] {
|
||||
const groups = wrapper.querySelectorAll('colgroup')
|
||||
const result: HTMLElement[] = []
|
||||
groups.forEach((g) => {
|
||||
const col = g.children[idx] as HTMLElement | undefined
|
||||
if (col) result.push(col)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
function setColumnWidth(wrapper: HTMLElement, colIndex: number, width: number) {
|
||||
const w = Math.max(40, width) + 'px'
|
||||
for (const col of allCols(wrapper, colIndex)) {
|
||||
col.style.width = w
|
||||
}
|
||||
}
|
||||
|
||||
// ── Event handlers ───────────────────────────────────────
|
||||
|
||||
function onMouseMove(e: MouseEvent) {
|
||||
const diff = e.clientX - drag.startX
|
||||
setColumnWidth(drag.tableWrapper, drag.colIndex, drag.startWidth + diff)
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
document.removeEventListener('mousemove', onMouseMove)
|
||||
document.removeEventListener('mouseup', onMouseUp)
|
||||
document.body.style.cursor = ''
|
||||
document.body.style.userSelect = ''
|
||||
const handle = drag.th.querySelector('.col-resize-handle') as HTMLElement | null
|
||||
if (handle) handle.classList.remove('is-resizing')
|
||||
}
|
||||
|
||||
function onHandleDown(e: MouseEvent, th: HTMLElement, wrapper: HTMLElement) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const idx = getColIndex(th)
|
||||
const cols = allCols(wrapper, idx)
|
||||
const currentWidth = cols[0] ? cols[0].getBoundingClientRect().width : th.offsetWidth
|
||||
|
||||
drag.startX = e.clientX
|
||||
drag.startWidth = currentWidth
|
||||
drag.th = th
|
||||
drag.tableWrapper = wrapper
|
||||
drag.colIndex = idx
|
||||
|
||||
const handle = th.querySelector('.col-resize-handle') as HTMLElement | null
|
||||
if (handle) handle.classList.add('is-resizing')
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove)
|
||||
document.addEventListener('mouseup', onMouseUp)
|
||||
document.body.style.cursor = 'col-resize'
|
||||
document.body.style.userSelect = 'none'
|
||||
}
|
||||
|
||||
// ── Setup / Teardown ─────────────────────────────────────
|
||||
|
||||
function injectHandles(wrapper: HTMLElement) {
|
||||
const headerThs = wrapper.querySelectorAll<HTMLElement>(
|
||||
'.el-table__header-wrapper th:not(.el-table__cell--selection)',
|
||||
)
|
||||
if (headerThs.length === 0) return
|
||||
|
||||
headerThs.forEach((th) => {
|
||||
// Don't add handle to the last column (or to columns that already have one)
|
||||
// Actually, allow all columns except the last to be resizable
|
||||
if (th.querySelector('.col-resize-handle')) return
|
||||
|
||||
const handle = document.createElement('div')
|
||||
handle.className = 'col-resize-handle'
|
||||
handle.addEventListener('mousedown', (e: MouseEvent) => onHandleDown(e, th, wrapper))
|
||||
// Prevent sort trigger on header click when grabbing handle
|
||||
handle.addEventListener('click', (e: Event) => e.stopPropagation())
|
||||
th.appendChild(handle)
|
||||
})
|
||||
}
|
||||
|
||||
// ── Directive definition ─────────────────────────────────
|
||||
|
||||
function findWrapper(el: HTMLElement): HTMLElement | null {
|
||||
return el.querySelector<HTMLElement>('.el-table__inner-wrapper')
|
||||
|| el.querySelector<HTMLElement>('.el-table__header-wrapper')?.closest('.el-table') as HTMLElement
|
||||
|| null
|
||||
}
|
||||
|
||||
export const vColumnResize: Directive<HTMLElement> = {
|
||||
mounted(el: HTMLElement, _binding: DirectiveBinding) {
|
||||
const trySetup = () => {
|
||||
const wrapper = findWrapper(el)
|
||||
if (wrapper) {
|
||||
injectHandles(wrapper)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if (!trySetup()) {
|
||||
const timer = setTimeout(() => trySetup(), 200)
|
||||
;(el as any).__colResizeTimer = timer
|
||||
}
|
||||
// Re-inject handles when DOM changes (sort, filter, data reload)
|
||||
const observer = new MutationObserver(() => {
|
||||
const wrapper = findWrapper(el)
|
||||
if (wrapper) injectHandles(wrapper)
|
||||
})
|
||||
observer.observe(el, { childList: true, subtree: true })
|
||||
;(el as any).__colResizeObserver = observer
|
||||
},
|
||||
|
||||
unmounted(el: HTMLElement) {
|
||||
clearTimeout((el as any).__colResizeTimer)
|
||||
const observer = (el as any).__colResizeObserver as MutationObserver | undefined
|
||||
observer?.disconnect()
|
||||
},
|
||||
}
|
||||
|
||||
export default vColumnResize
|
||||
Reference in New Issue
Block a user