This commit is contained in:
zwgczx committed 2026-02-26 15:55:36 +08:00
1 parent 1609f19b9c
commit 37f4a99914
14 files changed
+953 -427

No files matched your search

+85 -32
View File
@@ -5,38 +5,12 @@ import type { ColDef, GridOptions } from 'ag-grid-community'
import localforage from 'localforage'
import { majorList } from '@/sql'
import 'ag-grid-enterprise'
import {
import { myTheme } from '@/lib/diyAgGridTheme'
themeQuartz
} from "ag-grid-community"
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
// 精简的边框配置(细线条+浅灰色,弱化分割线视觉)
const borderConfig = {
style: "solid", // 虚线改实线更简洁,也可保留 dotted 但建议用 solid
width: 0.5, // 更细的边框,减少视觉干扰
color: "#e5e7eb" // 浅灰色边框,清新不刺眼
};
// 简洁清新风格的主题配置
const myTheme = themeQuartz.withParams({
// 核心:移除外边框,减少视觉包裹感
wrapperBorder: false,
// 表头样式(柔和浅蓝,无加粗,更轻盈)
headerBackgroundColor: "#f9fafb", // 极浅的背景色,替代深一点的 #e7f3fc
headerTextColor: "#374151", // 深灰色文字,比纯黑更柔和
headerFontSize: 15, // 字体稍大一点,更易读
headerFontWeight: "normal", // 取消加粗,降低视觉重量
// 行/列/表头边框(统一浅灰细边框)
rowBorder: borderConfig,
columnBorder: borderConfig,
headerRowBorder: borderConfig,
// 可选:偶数行背景色(轻微区分,更清新)
dataBackgroundColor: "#fefefe"
});
interface DictLeaf {
id: string
code: string
@@ -71,6 +45,73 @@ const DEFAULT_PROJECT_NAME = 'xxx造价咨询服务'
const projectName = ref(DEFAULT_PROJECT_NAME)
const detailRows = ref<DetailRow[]>([])
const rootRef = ref<HTMLElement | null>(null)
const gridSectionRef = ref<HTMLElement | null>(null)
const agGridRef = ref<HTMLElement | null>(null)
const agGridHeight = ref(580)
let snapScrollHost: HTMLElement | null = null
let snapTimer: ReturnType<typeof setTimeout> | null = null
let snapLockTimer: ReturnType<typeof setTimeout> | null = null
let isSnapping = false
let hostResizeObserver: ResizeObserver | null = null
const updateGridCardHeight = () => {
if (!snapScrollHost || !rootRef.value) return
const contentWrap = rootRef.value.parentElement
const style = contentWrap ? window.getComputedStyle(contentWrap) : null
const paddingTop = style ? Number.parseFloat(style.paddingTop || '0') || 0 : 0
const paddingBottom = style ? Number.parseFloat(style.paddingBottom || '0') || 0 : 0
const nextHeight = Math.max(360, Math.floor(snapScrollHost.clientHeight - paddingTop - paddingBottom))
agGridHeight.value = nextHeight
}
const bindSnapScrollHost = () => {
snapScrollHost = rootRef.value?.closest('[data-slot="scroll-area-viewport"]') as HTMLElement | null
if (!snapScrollHost) return
snapScrollHost.addEventListener('scroll', handleSnapHostScroll, { passive: true })
hostResizeObserver?.disconnect()
hostResizeObserver = new ResizeObserver(() => {
updateGridCardHeight()
})
hostResizeObserver.observe(snapScrollHost)
updateGridCardHeight()
}
const unbindSnapScrollHost = () => {
if (snapScrollHost) {
snapScrollHost.removeEventListener('scroll', handleSnapHostScroll)
}
hostResizeObserver?.disconnect()
hostResizeObserver = null
snapScrollHost = null
}
const trySnapToGrid = () => {
if (isSnapping || !snapScrollHost || !agGridRef.value) return
const hostRect = snapScrollHost.getBoundingClientRect()
const gridRect = agGridRef.value.getBoundingClientRect()
const offsetTop = gridRect.top - hostRect.top
const inVisibleBand = gridRect.bottom > hostRect.top + 40 && gridRect.top < hostRect.bottom - 40
const inSnapRange = offsetTop > -120 && offsetTop < 180
if (!inVisibleBand || !inSnapRange) return
isSnapping = true
agGridRef.value.scrollIntoView({ behavior: 'smooth', block: 'start' })
if (snapLockTimer) clearTimeout(snapLockTimer)
snapLockTimer = setTimeout(() => {
isSnapping = false
}, 420)
}
function handleSnapHostScroll() {
if (isSnapping) return
if (snapTimer) clearTimeout(snapTimer)
snapTimer = setTimeout(() => {
trySnapToGrid()
}, 90)
}
type MajorLite = { code: string; name: string }
const majorEntries = Object.entries(majorList as Record<string, MajorLite>)
.sort((a, b) => Number(a[0]) - Number(b[0]))
@@ -330,13 +371,20 @@ watch(projectName, schedulePersist)
onMounted(async () => {
await loadFromIndexedDB()
bindSnapScrollHost()
requestAnimationFrame(() => {
updateGridCardHeight()
})
// window.addEventListener('beforeunload', handleBeforeUnload)
})
onBeforeUnmount(() => {
// window.removeEventListener('beforeunload', handleBeforeUnload)
unbindSnapScrollHost()
if (persistTimer) clearTimeout(persistTimer)
if (gridPersistTimer) clearTimeout(gridPersistTimer)
if (snapTimer) clearTimeout(snapTimer)
if (snapLockTimer) clearTimeout(snapLockTimer)
void saveToIndexedDB()
})
const processCellForClipboard = (params:any) => {
@@ -358,9 +406,11 @@ const processCellFromClipboard = (params:any) => {
</script>
<template>
<div class="space-y-6">
<div class="rounded-lg border bg-card p-4 shadow-sm">
<label class="mb-2 block text-sm font-medium text-foreground">项目名称</label>
<div ref="rootRef" class="space-y-6">
<div class="rounded-lg border bg-card p-4 shadow-sm shrink-0">
<div class="mb-2 flex items-center justify-between gap-3">
<label class="block text-sm font-medium text-foreground">项目名称</label>
</div>
<input
v-model="projectName"
type="text"
@@ -369,13 +419,16 @@ const processCellFromClipboard = (params:any) => {
/>
</div>
<div class="rounded-lg border bg-card xmMx">
<div
ref="gridSectionRef"
class="rounded-lg border bg-card xmMx scroll-mt-3"
>
<div class="flex items-center justify-between border-b px-4 py-3">
<h3 class="text-sm font-semibold text-foreground">项目明细</h3>
<div class="text-xs text-muted-foreground">导入导出</div>
</div>
<div class="ag-theme-quartz h-[580px] w-full">
<div ref="agGridRef" class="ag-theme-quartz w-full" :style="{ height: `${agGridHeight}px` }">
<AgGridVue
:style="{ height: '100%' }"
:rowData="detailRows"