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:
2026-07-11 22:17:45 +08:00
parent deeafe239f
commit e8b92a6f99
17 changed files with 767 additions and 156 deletions
+72
View File
@@ -0,0 +1,72 @@
/**
* useScreenshot — capture a DOM element as PNG and trigger download.
*
* Uses dom-to-image-more (SVG foreignObject) for pixel-perfect text rendering —
* unlike html2canvas, the browser itself paints the text so there's zero offset.
*
* Usage:
* const { capturing, captureEl } = useScreenshot()
* <div ref="shotRef">...</div>
* <button @click="captureEl(shotRef, 'filename')">截图</button>
*/
import { ref, nextTick } from 'vue'
import domtoimage from 'dom-to-image-more'
export function useScreenshot() {
const capturing = ref(false)
async function captureEl(el: HTMLElement | null, filename: string): Promise<boolean> {
if (!el || capturing.value) return false
capturing.value = true
await nextTick()
el.classList.add('taking-screenshot')
document.body.classList.add('body-capturing')
await new Promise((r) => setTimeout(r, 80))
try {
const dataUrl = await domtoimage.toPng(el, {
bgColor: '#ffffff',
width: el.scrollWidth,
height: el.scrollHeight,
quality: 1,
cacheBust: true,
})
// Pad 5px white border around the image
const img = await new Promise<HTMLImageElement>((resolve, reject) => {
const i = new Image()
i.onload = () => resolve(i)
i.onerror = reject
i.src = dataUrl
})
const PAD = 5
const canvas = document.createElement('canvas')
canvas.width = img.width + PAD * 2
canvas.height = img.height + PAD * 2
const ctx = canvas.getContext('2d')!
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(img, PAD, PAD)
const link = document.createElement('a')
link.download = filename
link.href = canvas.toDataURL('image/png')
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
return true
} catch (e) {
console.error('Screenshot failed:', e)
return false
} finally {
el.classList.remove('taking-screenshot')
document.body.classList.remove('body-capturing')
capturing.value = false
}
}
return { capturing, captureEl }
}