From e97707ac595b6af93946036ce2f08b4bf4f343ef Mon Sep 17 00:00:00 2001 From: wintsa <770775984@qq.com> Date: Fri, 27 Feb 2026 18:25:19 +0800 Subject: [PATCH] fix --- src/components/views/Ht.vue | 109 ++++++++++++++---- .../InvestmentScalePricingPane.vue | 8 +- .../pricingView/LandScalePricingPane.vue | 8 +- .../views/pricingView/WorkloadPricingPane.vue | 62 +++++----- src/components/views/xmInfo.vue | 13 ++- src/components/views/zxFw.vue | 12 +- 6 files changed, 153 insertions(+), 59 deletions(-) diff --git a/src/components/views/Ht.vue b/src/components/views/Ht.vue index b53a2bb..fce5637 100644 --- a/src/components/views/Ht.vue +++ b/src/components/views/Ht.vue @@ -7,7 +7,7 @@ import { Button } from '@/components/ui/button' import { ScrollArea } from '@/components/ui/scroll-area' import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip' import { useTabStore } from '@/pinia/tab' -import { Edit3, GripVertical, Plus, Trash2, X } from 'lucide-vue-next' +import { ArrowUp, Edit3, GripVertical, Plus, Trash2, X } from 'lucide-vue-next' import { ToastAction, ToastDescription, @@ -51,16 +51,19 @@ let baseOffsetX = 0 let baseOffsetY = 0 const contractListScrollWrapRef = ref(null) const contractListViewportRef = ref(null) +const showScrollTopFab = ref(false) const isDraggingContracts = ref(false) const cardMotionState = ref<'enter' | 'ready'>('ready') let contractAutoScrollRaf = 0 let dragPointerClientY: number | null = null let cardEnterTimer: ReturnType | null = null +let contractListScrollBoundEl: HTMLElement | null = null const CARD_ENTER_STEP_MS = 58 const CARD_ENTER_DURATION_MS = 560 const CARD_ENTER_MAX_INDEX = 24 const CARD_ENTER_TOTAL_MS = CARD_ENTER_DURATION_MS + CARD_ENTER_STEP_MS * CARD_ENTER_MAX_INDEX + 80 +const SCROLL_TOP_FAB_THRESHOLD = 220 const buildDefaultContracts = (): ContractItem[] => [ @@ -118,6 +121,51 @@ const scrollContractsToBottom = (behavior: ScrollBehavior = 'smooth') => { }) } +const scrollContractsToTop = (behavior: ScrollBehavior = 'smooth') => { + const viewport = getContractListViewport() + if (!viewport) return + viewport.scrollTo({ + top: 0, + behavior + }) +} + +const updateScrollTopFabVisible = () => { + const viewport = contractListViewportRef.value + showScrollTopFab.value = Boolean(viewport && viewport.scrollTop > SCROLL_TOP_FAB_THRESHOLD) +} + +const handleContractListScroll = () => { + updateScrollTopFabVisible() +} + +const bindContractListScroll = () => { + const viewport = getContractListViewport() + if (contractListScrollBoundEl === viewport) { + updateScrollTopFabVisible() + return + } + if (contractListScrollBoundEl) { + contractListScrollBoundEl.removeEventListener('scroll', handleContractListScroll) + contractListScrollBoundEl = null + } + if (!viewport) { + showScrollTopFab.value = false + return + } + contractListScrollBoundEl = viewport + contractListScrollBoundEl.addEventListener('scroll', handleContractListScroll, { passive: true }) + updateScrollTopFabVisible() +} + +const unbindContractListScroll = () => { + if (contractListScrollBoundEl) { + contractListScrollBoundEl.removeEventListener('scroll', handleContractListScroll) + contractListScrollBoundEl = null + } + showScrollTopFab.value = false +} + const triggerCardEnterAnimation = () => { if (cardEnterTimer) { clearTimeout(cardEnterTimer) @@ -379,19 +427,20 @@ onMounted(async () => { await loadContracts() triggerCardEnterAnimation() await nextTick() - getContractListViewport() + bindContractListScroll() }) onActivated(() => { triggerCardEnterAnimation() void nextTick(() => { - getContractListViewport() + bindContractListScroll() }) }) onBeforeUnmount(() => { stopDrag() stopContractAutoScroll() + unbindContractListScroll() if (cardEnterTimer) clearTimeout(cardEnterTimer) void saveContracts() }) @@ -410,13 +459,29 @@ onBeforeUnmount(() => { -
- +
+
+
+ + +
+
+ 搜索中({{ filteredContracts.length }} / {{ contracts.length }}),已关闭拖拽排序 +
+
- -
- 搜索中({{ filteredContracts.length }} / {{ contracts.length }}),已关闭拖拽排序 -
@@ -664,6 +718,19 @@ onBeforeUnmount(() => { + +
{ const formatReadonlyNumber = (params: any) => { if (params.value == null || params.value === '') return '' - return Number(params.value).toFixed(2) + return roundTo(params.value, 2).toFixed(2) } const getBenchmarkBudgetByAmount = (row?: Pick) => { const result = getBasicFeeFromScale(row?.amount, 'cost') - return result ? addNumbers(result.basic, result.optional) : null + return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null } const getBudgetFee = (row?: Pick) => { const benchmarkBudget = getBenchmarkBudgetByAmount(row) if (benchmarkBudget == null || row?.majorFactor == null || row?.consultCategoryFactor == null) return null - return benchmarkBudget * row.majorFactor * row.consultCategoryFactor + return roundTo(toDecimal(benchmarkBudget).mul(row.majorFactor).mul(row.consultCategoryFactor), 2) } const columnDefs: ColDef[] = [ diff --git a/src/components/views/pricingView/LandScalePricingPane.vue b/src/components/views/pricingView/LandScalePricingPane.vue index cb82658..f8c941a 100644 --- a/src/components/views/pricingView/LandScalePricingPane.vue +++ b/src/components/views/pricingView/LandScalePricingPane.vue @@ -5,7 +5,7 @@ import type { ColDef } from 'ag-grid-community' import localforage from 'localforage' import { getBasicFeeFromScale, majorList, serviceList } from '@/sql' import { myTheme, gridOptions } from '@/lib/diyAgGridOptions' -import { addNumbers, decimalAggSum, sumByNumber } from '@/lib/decimal' +import { addNumbers, decimalAggSum, roundTo, sumByNumber, toDecimal } from '@/lib/decimal' import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload' import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'; @@ -237,18 +237,18 @@ const formatMajorFactor = (params: any) => { const formatReadonlyNumber = (params: any) => { if (params.value == null || params.value === '') return '' - return Number(params.value).toFixed(2) + return roundTo(params.value, 2).toFixed(2) } const getBenchmarkBudgetByLandArea = (row?: Pick) => { const result = getBasicFeeFromScale(row?.landArea, 'area') - return result ? addNumbers(result.basic, result.optional) : null + return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null } const getBudgetFee = (row?: Pick) => { const benchmarkBudget = getBenchmarkBudgetByLandArea(row) if (benchmarkBudget == null || row?.majorFactor == null || row?.consultCategoryFactor == null) return null - return benchmarkBudget * row.majorFactor * row.consultCategoryFactor + return roundTo(toDecimal(benchmarkBudget).mul(row.majorFactor).mul(row.consultCategoryFactor), 2) } const formatEditableFlexibleNumber = (params: any) => { diff --git a/src/components/views/pricingView/WorkloadPricingPane.vue b/src/components/views/pricingView/WorkloadPricingPane.vue index f7d79bd..e71efbe 100644 --- a/src/components/views/pricingView/WorkloadPricingPane.vue +++ b/src/components/views/pricingView/WorkloadPricingPane.vue @@ -5,7 +5,7 @@ import type { ColDef } from 'ag-grid-community' import localforage from 'localforage' import { serviceList, taskList } from '@/sql' import { myTheme, gridOptions } from '@/lib/diyAgGridOptions' -import { decimalAggSum } from '@/lib/decimal' +import { decimalAggSum, roundTo, toDecimal } from '@/lib/decimal' import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload' import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'; @@ -91,14 +91,20 @@ const formatTaskReferenceUnitPrice = (task: taskLite) => { return '' } -const buildDefaultRows = (): DetailRow[] => { - const rows: DetailRow[] = [] +const getSourceTaskIds = () => { const currentServiceId = Number(props.serviceId) - const sourceTaskIds = Object.entries(taskList as Record) + return Object.entries(taskList as Record) .filter(([, task]) => Number(task.serviceID) === currentServiceId) .map(([key]) => Number(key)) .filter(Number.isFinite) .sort((a, b) => a - b) +} + +const isWorkloadMethodApplicable = computed(() => getSourceTaskIds().length > 0) + +const buildDefaultRows = (): DetailRow[] => { + const rows: DetailRow[] = [] + const sourceTaskIds = getSourceTaskIds() for (const [order, taskId] of sourceTaskIds.entries()) { const task = (taskList as Record)[String(taskId)] @@ -122,25 +128,6 @@ const buildDefaultRows = (): DetailRow[] => { }) } - if (rows.length === 0) { - const emptyRowId = `task-none-${String(props.serviceId)}` - rows.push({ - id: emptyRowId, - taskCode: '无', - taskName: '无', - unit: '', - conversion: null, - workload: null, - budgetBase: '无', - budgetReferenceUnitPrice: '无', - budgetAdoptedUnitPrice: null, - consultCategoryFactor: null, - serviceFee: null, - remark: '', - path: [emptyRowId] - }) - } - return rows } @@ -194,7 +181,7 @@ const calcServiceFee = (row: DetailRow | undefined) => { ) { return null } - return price * conversion * workload * factor + return roundTo(toDecimal(price).mul(conversion).mul(workload).mul(factor), 2) } const formatEditableNumber = (params: any) => { @@ -206,6 +193,12 @@ const formatEditableNumber = (params: any) => { return Number(params.value).toFixed(2) } +const formatReadonlyNumber = (params: any) => { + if (isNoTaskRow(params.data)) return '无' + if (params.value == null || params.value === '') return '' + return roundTo(params.value, 2).toFixed(2) +} + const spanRowsByTaskName = (params: any) => { const rowA = params?.nodeA?.data as DetailRow | undefined const rowB = params?.nodeB?.data as DetailRow | undefined @@ -322,7 +315,7 @@ const columnDefs: ColDef[] = [ editable: false, valueGetter: params => calcServiceFee(params.data), aggFunc: decimalAggSum, - // valueFormatter: formatEditableNumber + valueFormatter: formatReadonlyNumber }, { headerName: '说明', @@ -353,6 +346,7 @@ const columnDefs: ColDef[] = [ const saveToIndexedDB = async () => { + if (!isWorkloadMethodApplicable.value) return if (shouldSkipPersist()) return try { const payload = { @@ -367,6 +361,11 @@ const saveToIndexedDB = async () => { const loadFromIndexedDB = async () => { try { + if (!isWorkloadMethodApplicable.value) { + detailRows.value = [] + return + } + if (shouldForceDefaultLoad()) { detailRows.value = buildDefaultRows() return @@ -451,11 +450,11 @@ const mydiyTheme = myTheme.withParams({
-

工作流规模

+

工作量明细

导入导出
-
+
+
+
+

该服务不适用工作量法

+

当前服务没有关联工作量法任务,无需填写此部分内容。

+
+
diff --git a/src/components/views/xmInfo.vue b/src/components/views/xmInfo.vue index fe8cbda..240cfbe 100644 --- a/src/components/views/xmInfo.vue +++ b/src/components/views/xmInfo.vue @@ -384,6 +384,11 @@ const processCellFromClipboard = (params:any) => { } return params.value; }; + +const scrollToGridSection = () => { + const target = gridSectionRef.value || agGridRef.value + target?.scrollIntoView({ behavior: 'smooth', block: 'start' }) +} - diff --git a/src/components/views/zxFw.vue b/src/components/views/zxFw.vue index e225d49..0120083 100644 --- a/src/components/views/zxFw.vue +++ b/src/components/views/zxFw.vue @@ -571,6 +571,11 @@ const handleCellValueChanged = () => { }, 1000) } +const scrollToGridSection = () => { + const target = gridSectionRef.value || agGridRef.value + target?.scrollIntoView({ behavior: 'smooth', block: 'start' }) +} + onMounted(async () => { await loadFromIndexedDB() bindSnapScrollHost() @@ -668,7 +673,12 @@ onBeforeUnmount(() => { class="rounded-lg border bg-card xmMx scroll-mt-3" :style="{ height: `${agGridHeight}px` }" >
-

咨询服务明细

+

+ 咨询服务明细 +

按服务词典生成