fix
This commit is contained in:
parent
9849801e46
commit
e97707ac59
@ -7,7 +7,7 @@ import { Button } from '@/components/ui/button'
|
|||||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||||
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip'
|
import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/components/ui/tooltip'
|
||||||
import { useTabStore } from '@/pinia/tab'
|
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 {
|
import {
|
||||||
ToastAction,
|
ToastAction,
|
||||||
ToastDescription,
|
ToastDescription,
|
||||||
@ -51,16 +51,19 @@ let baseOffsetX = 0
|
|||||||
let baseOffsetY = 0
|
let baseOffsetY = 0
|
||||||
const contractListScrollWrapRef = ref<HTMLElement | null>(null)
|
const contractListScrollWrapRef = ref<HTMLElement | null>(null)
|
||||||
const contractListViewportRef = ref<HTMLElement | null>(null)
|
const contractListViewportRef = ref<HTMLElement | null>(null)
|
||||||
|
const showScrollTopFab = ref(false)
|
||||||
const isDraggingContracts = ref(false)
|
const isDraggingContracts = ref(false)
|
||||||
const cardMotionState = ref<'enter' | 'ready'>('ready')
|
const cardMotionState = ref<'enter' | 'ready'>('ready')
|
||||||
let contractAutoScrollRaf = 0
|
let contractAutoScrollRaf = 0
|
||||||
let dragPointerClientY: number | null = null
|
let dragPointerClientY: number | null = null
|
||||||
let cardEnterTimer: ReturnType<typeof setTimeout> | null = null
|
let cardEnterTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
let contractListScrollBoundEl: HTMLElement | null = null
|
||||||
|
|
||||||
const CARD_ENTER_STEP_MS = 58
|
const CARD_ENTER_STEP_MS = 58
|
||||||
const CARD_ENTER_DURATION_MS = 560
|
const CARD_ENTER_DURATION_MS = 560
|
||||||
const CARD_ENTER_MAX_INDEX = 24
|
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 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[] => [
|
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 = () => {
|
const triggerCardEnterAnimation = () => {
|
||||||
if (cardEnterTimer) {
|
if (cardEnterTimer) {
|
||||||
clearTimeout(cardEnterTimer)
|
clearTimeout(cardEnterTimer)
|
||||||
@ -379,19 +427,20 @@ onMounted(async () => {
|
|||||||
await loadContracts()
|
await loadContracts()
|
||||||
triggerCardEnterAnimation()
|
triggerCardEnterAnimation()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
getContractListViewport()
|
bindContractListScroll()
|
||||||
})
|
})
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
triggerCardEnterAnimation()
|
triggerCardEnterAnimation()
|
||||||
void nextTick(() => {
|
void nextTick(() => {
|
||||||
getContractListViewport()
|
bindContractListScroll()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
stopDrag()
|
stopDrag()
|
||||||
stopContractAutoScroll()
|
stopContractAutoScroll()
|
||||||
|
unbindContractListScroll()
|
||||||
if (cardEnterTimer) clearTimeout(cardEnterTimer)
|
if (cardEnterTimer) clearTimeout(cardEnterTimer)
|
||||||
void saveContracts()
|
void saveContracts()
|
||||||
})
|
})
|
||||||
@ -410,13 +459,29 @@ onBeforeUnmount(() => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 md:flex-row md:items-center">
|
<div class="flex flex-col gap-2 md:flex-row md:items-start">
|
||||||
<input
|
<div class="w-full md:max-w-md">
|
||||||
v-model="contractSearchKeyword"
|
<div class="flex items-center gap-2">
|
||||||
type="text"
|
<input
|
||||||
placeholder="搜索合同段名称或ID"
|
v-model="contractSearchKeyword"
|
||||||
class="h-10 w-full rounded-md border bg-background px-3 text-sm outline-none transition focus-visible:ring-2 focus-visible:ring-ring md:max-w-md"
|
type="text"
|
||||||
/>
|
placeholder="搜索合同段名称或ID"
|
||||||
|
class="h-10 w-full rounded-md border bg-background px-3 text-sm outline-none transition focus-visible:ring-2 focus-visible:ring-ring"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
v-if="contractSearchKeyword"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
class="h-10 shrink-0 px-3"
|
||||||
|
@click="contractSearchKeyword = ''"
|
||||||
|
>
|
||||||
|
清空筛选
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div v-if="isSearchingContracts" class="mt-1 text-xs text-muted-foreground">
|
||||||
|
搜索中({{ filteredContracts.length }} / {{ contracts.length }}),已关闭拖拽排序
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="flex flex-wrap items-center gap-2 md:ml-auto">
|
<div class="flex flex-wrap items-center gap-2 md:ml-auto">
|
||||||
<label class="inline-flex cursor-pointer items-center gap-2 text-xs text-muted-foreground select-none">
|
<label class="inline-flex cursor-pointer items-center gap-2 text-xs text-muted-foreground select-none">
|
||||||
<span>{{ isListLayout ? '列表布局' : '网格布局' }}</span>
|
<span>{{ isListLayout ? '列表布局' : '网格布局' }}</span>
|
||||||
@ -434,17 +499,6 @@ onBeforeUnmount(() => {
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
<Button
|
|
||||||
v-if="contractSearchKeyword"
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
@click="contractSearchKeyword = ''"
|
|
||||||
>
|
|
||||||
清空筛选
|
|
||||||
</Button>
|
|
||||||
<div v-if="isSearchingContracts" class="text-xs text-muted-foreground">
|
|
||||||
搜索中({{ filteredContracts.length }} / {{ contracts.length }}),已关闭拖拽排序
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -664,6 +718,19 @@ onBeforeUnmount(() => {
|
|||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="回到顶部"
|
||||||
|
aria-label="回到顶部"
|
||||||
|
:class="[
|
||||||
|
'fixed bottom-8 right-8 z-40 inline-flex h-11 w-11 cursor-pointer items-center justify-center rounded-full border border-black/15 bg-white text-black shadow-[0_10px_24px_rgba(0,0,0,0.16)] transition-all duration-300 hover:scale-105 hover:border-black/30 hover:bg-black hover:text-white',
|
||||||
|
showScrollTopFab ? 'translate-y-0 opacity-100' : 'pointer-events-none translate-y-3 opacity-0'
|
||||||
|
]"
|
||||||
|
@click="scrollContractsToTop()"
|
||||||
|
>
|
||||||
|
<ArrowUp class="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="showCreateModal"
|
v-if="showCreateModal"
|
||||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4"
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4"
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type { ColDef } from 'ag-grid-community'
|
|||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { getBasicFeeFromScale, majorList, serviceList } from '@/sql'
|
import { getBasicFeeFromScale, majorList, serviceList } from '@/sql'
|
||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
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 { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
||||||
|
|
||||||
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
|
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
|
||||||
@ -234,18 +234,18 @@ const formatMajorFactor = (params: any) => {
|
|||||||
|
|
||||||
const formatReadonlyNumber = (params: any) => {
|
const formatReadonlyNumber = (params: any) => {
|
||||||
if (params.value == null || params.value === '') return ''
|
if (params.value == null || params.value === '') return ''
|
||||||
return Number(params.value).toFixed(2)
|
return roundTo(params.value, 2).toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBenchmarkBudgetByAmount = (row?: Pick<DetailRow, 'amount'>) => {
|
const getBenchmarkBudgetByAmount = (row?: Pick<DetailRow, 'amount'>) => {
|
||||||
const result = getBasicFeeFromScale(row?.amount, 'cost')
|
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<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
const getBudgetFee = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||||
const benchmarkBudget = getBenchmarkBudgetByAmount(row)
|
const benchmarkBudget = getBenchmarkBudgetByAmount(row)
|
||||||
if (benchmarkBudget == null || row?.majorFactor == null || row?.consultCategoryFactor == null) return null
|
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<DetailRow>[] = [
|
const columnDefs: ColDef<DetailRow>[] = [
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type { ColDef } from 'ag-grid-community'
|
|||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { getBasicFeeFromScale, majorList, serviceList } from '@/sql'
|
import { getBasicFeeFromScale, majorList, serviceList } from '@/sql'
|
||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
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 { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
||||||
|
|
||||||
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
|
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
|
||||||
@ -237,18 +237,18 @@ const formatMajorFactor = (params: any) => {
|
|||||||
|
|
||||||
const formatReadonlyNumber = (params: any) => {
|
const formatReadonlyNumber = (params: any) => {
|
||||||
if (params.value == null || params.value === '') return ''
|
if (params.value == null || params.value === '') return ''
|
||||||
return Number(params.value).toFixed(2)
|
return roundTo(params.value, 2).toFixed(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBenchmarkBudgetByLandArea = (row?: Pick<DetailRow, 'landArea'>) => {
|
const getBenchmarkBudgetByLandArea = (row?: Pick<DetailRow, 'landArea'>) => {
|
||||||
const result = getBasicFeeFromScale(row?.landArea, 'area')
|
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<DetailRow, 'landArea' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
const getBudgetFee = (row?: Pick<DetailRow, 'landArea' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||||
const benchmarkBudget = getBenchmarkBudgetByLandArea(row)
|
const benchmarkBudget = getBenchmarkBudgetByLandArea(row)
|
||||||
if (benchmarkBudget == null || row?.majorFactor == null || row?.consultCategoryFactor == null) return null
|
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) => {
|
const formatEditableFlexibleNumber = (params: any) => {
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type { ColDef } from 'ag-grid-community'
|
|||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { serviceList, taskList } from '@/sql'
|
import { serviceList, taskList } from '@/sql'
|
||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
import { decimalAggSum } from '@/lib/decimal'
|
import { decimalAggSum, roundTo, toDecimal } from '@/lib/decimal'
|
||||||
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
||||||
|
|
||||||
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
|
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
|
||||||
@ -91,14 +91,20 @@ const formatTaskReferenceUnitPrice = (task: taskLite) => {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildDefaultRows = (): DetailRow[] => {
|
const getSourceTaskIds = () => {
|
||||||
const rows: DetailRow[] = []
|
|
||||||
const currentServiceId = Number(props.serviceId)
|
const currentServiceId = Number(props.serviceId)
|
||||||
const sourceTaskIds = Object.entries(taskList as Record<string, taskLite>)
|
return Object.entries(taskList as Record<string, taskLite>)
|
||||||
.filter(([, task]) => Number(task.serviceID) === currentServiceId)
|
.filter(([, task]) => Number(task.serviceID) === currentServiceId)
|
||||||
.map(([key]) => Number(key))
|
.map(([key]) => Number(key))
|
||||||
.filter(Number.isFinite)
|
.filter(Number.isFinite)
|
||||||
.sort((a, b) => a - b)
|
.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()) {
|
for (const [order, taskId] of sourceTaskIds.entries()) {
|
||||||
const task = (taskList as Record<string, taskLite | undefined>)[String(taskId)]
|
const task = (taskList as Record<string, taskLite | undefined>)[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
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +181,7 @@ const calcServiceFee = (row: DetailRow | undefined) => {
|
|||||||
) {
|
) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return price * conversion * workload * factor
|
return roundTo(toDecimal(price).mul(conversion).mul(workload).mul(factor), 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatEditableNumber = (params: any) => {
|
const formatEditableNumber = (params: any) => {
|
||||||
@ -206,6 +193,12 @@ const formatEditableNumber = (params: any) => {
|
|||||||
return Number(params.value).toFixed(2)
|
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 spanRowsByTaskName = (params: any) => {
|
||||||
const rowA = params?.nodeA?.data as DetailRow | undefined
|
const rowA = params?.nodeA?.data as DetailRow | undefined
|
||||||
const rowB = params?.nodeB?.data as DetailRow | undefined
|
const rowB = params?.nodeB?.data as DetailRow | undefined
|
||||||
@ -322,7 +315,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
editable: false,
|
editable: false,
|
||||||
valueGetter: params => calcServiceFee(params.data),
|
valueGetter: params => calcServiceFee(params.data),
|
||||||
aggFunc: decimalAggSum,
|
aggFunc: decimalAggSum,
|
||||||
// valueFormatter: formatEditableNumber
|
valueFormatter: formatReadonlyNumber
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerName: '说明',
|
headerName: '说明',
|
||||||
@ -353,6 +346,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
|
|
||||||
|
|
||||||
const saveToIndexedDB = async () => {
|
const saveToIndexedDB = async () => {
|
||||||
|
if (!isWorkloadMethodApplicable.value) return
|
||||||
if (shouldSkipPersist()) return
|
if (shouldSkipPersist()) return
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
@ -367,6 +361,11 @@ const saveToIndexedDB = async () => {
|
|||||||
|
|
||||||
const loadFromIndexedDB = async () => {
|
const loadFromIndexedDB = async () => {
|
||||||
try {
|
try {
|
||||||
|
if (!isWorkloadMethodApplicable.value) {
|
||||||
|
detailRows.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldForceDefaultLoad()) {
|
if (shouldForceDefaultLoad()) {
|
||||||
detailRows.value = buildDefaultRows()
|
detailRows.value = buildDefaultRows()
|
||||||
return
|
return
|
||||||
@ -451,11 +450,11 @@ const mydiyTheme = myTheme.withParams({
|
|||||||
|
|
||||||
<div class="rounded-lg border bg-card xmMx flex min-h-0 flex-1 flex-col">
|
<div class="rounded-lg border bg-card xmMx flex min-h-0 flex-1 flex-col">
|
||||||
<div class="flex items-center justify-between border-b px-4 py-3">
|
<div class="flex items-center justify-between border-b px-4 py-3">
|
||||||
<h3 class="text-sm font-semibold text-foreground">工作流规模</h3>
|
<h3 class="text-sm font-semibold text-foreground">工作量明细</h3>
|
||||||
<div class="text-xs text-muted-foreground">导入导出</div>
|
<div class="text-xs text-muted-foreground">导入导出</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ag-theme-quartz h-full min-h-0 w-full flex-1">
|
<div v-if="isWorkloadMethodApplicable" class="ag-theme-quartz h-full min-h-0 w-full flex-1">
|
||||||
<AgGridVue :style="{ height: '100%' }" :rowData="detailRows"
|
<AgGridVue :style="{ height: '100%' }" :rowData="detailRows"
|
||||||
:columnDefs="columnDefs" :gridOptions="gridOptions" :theme="mydiyTheme" :treeData="false"
|
:columnDefs="columnDefs" :gridOptions="gridOptions" :theme="mydiyTheme" :treeData="false"
|
||||||
:enableCellSpan="true"
|
:enableCellSpan="true"
|
||||||
@ -469,6 +468,15 @@ const mydiyTheme = myTheme.withParams({
|
|||||||
:processCellForClipboard="processCellForClipboard" :processCellFromClipboard="processCellFromClipboard"
|
:processCellForClipboard="processCellForClipboard" :processCellFromClipboard="processCellFromClipboard"
|
||||||
:undoRedoCellEditing="true" :undoRedoCellEditingLimit="20" />
|
:undoRedoCellEditing="true" :undoRedoCellEditingLimit="20" />
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
class="flex h-full min-h-0 w-full flex-1 items-center justify-center bg-[radial-gradient(circle_at_top,_rgba(220,38,38,0.18),rgba(0,0,0,0.03)_46%,transparent_72%)] p-6"
|
||||||
|
>
|
||||||
|
<div class="w-full max-w-xl rounded-2xl border border-red-300/85 bg-white/90 px-8 py-10 text-center shadow-[0_18px_38px_-22px_rgba(153,27,27,0.6)] backdrop-blur">
|
||||||
|
<p class="text-lg font-semibold tracking-wide text-neutral-900">该服务不适用工作量法</p>
|
||||||
|
<p class="mt-2 text-sm leading-6 text-red-700">当前服务没有关联工作量法任务,无需填写此部分内容。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -384,6 +384,11 @@ const processCellFromClipboard = (params:any) => {
|
|||||||
}
|
}
|
||||||
return params.value;
|
return params.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const scrollToGridSection = () => {
|
||||||
|
const target = gridSectionRef.value || agGridRef.value
|
||||||
|
target?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -409,7 +414,12 @@ const processCellFromClipboard = (params:any) => {
|
|||||||
:style="{ height: `${agGridHeight}px` }"
|
:style="{ height: `${agGridHeight}px` }"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between border-b px-4 py-3">
|
<div class="flex items-center justify-between border-b px-4 py-3">
|
||||||
<h3 class="text-sm font-semibold text-foreground">项目明细</h3>
|
<h3
|
||||||
|
class="text-sm font-semibold text-foreground cursor-pointer select-none transition-colors hover:text-primary"
|
||||||
|
@click="scrollToGridSection"
|
||||||
|
>
|
||||||
|
项目明细
|
||||||
|
</h3>
|
||||||
<div class="text-xs text-muted-foreground">导入导出</div>
|
<div class="text-xs text-muted-foreground">导入导出</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -440,4 +450,3 @@ const processCellFromClipboard = (params:any) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -571,6 +571,11 @@ const handleCellValueChanged = () => {
|
|||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scrollToGridSection = () => {
|
||||||
|
const target = gridSectionRef.value || agGridRef.value
|
||||||
|
target?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadFromIndexedDB()
|
await loadFromIndexedDB()
|
||||||
bindSnapScrollHost()
|
bindSnapScrollHost()
|
||||||
@ -668,7 +673,12 @@ onBeforeUnmount(() => {
|
|||||||
class="rounded-lg border bg-card xmMx scroll-mt-3" :style="{ height: `${agGridHeight}px` }"
|
class="rounded-lg border bg-card xmMx scroll-mt-3" :style="{ height: `${agGridHeight}px` }"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between border-b px-4 py-3">
|
<div class="flex items-center justify-between border-b px-4 py-3">
|
||||||
<h3 class="text-sm font-semibold text-foreground">咨询服务明细</h3>
|
<h3
|
||||||
|
class="text-sm font-semibold text-foreground cursor-pointer select-none transition-colors hover:text-primary"
|
||||||
|
@click="scrollToGridSection"
|
||||||
|
>
|
||||||
|
咨询服务明细
|
||||||
|
</h3>
|
||||||
<div class="text-xs text-muted-foreground">按服务词典生成</div>
|
<div class="text-xs text-muted-foreground">按服务词典生成</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user