This commit is contained in:
wintsa 2026-03-06 17:40:08 +08:00
parent 626513bc21
commit c482faacbf
9 changed files with 198 additions and 47 deletions

View File

@ -35,6 +35,7 @@ type DictSource = Record<string, DictItem>
const props = defineProps<{
title: string
storageKey: string
parentStorageKey?: string
dict: DictSource
disableBudgetEditWhenStandardNull?: boolean
excludeNotshowByZxflxs?: boolean
@ -219,13 +220,31 @@ const saveToIndexedDB = async () => {
}
}
const loadGridState = async (storageKey: string): Promise<GridState | null> => {
if (!storageKey) return null
const data = await localforage.getItem<GridState>(storageKey)
if (!data?.detailRows || !Array.isArray(data.detailRows)) return null
return data
}
const loadFromIndexedDB = async () => {
try {
const data = await localforage.getItem<GridState>(props.storageKey)
if (data?.detailRows) {
const data = await loadGridState(props.storageKey)
if (data) {
detailRows.value = mergeWithDictRows(data.detailRows)
return
}
const parentStorageKey = props.parentStorageKey?.trim()
if (parentStorageKey) {
const parentData = await loadGridState(parentStorageKey)
if (parentData) {
detailRows.value = mergeWithDictRows(parentData.detailRows)
await saveToIndexedDB()
return
}
}
detailRows.value = buildDefaultRows()
} catch (error) {
console.error('loadFromIndexedDB failed:', error)

View File

@ -28,18 +28,7 @@ interface DictGroup {
children: DictLeaf[]
}
interface DetailRow {
id: string
groupCode: string
groupName: string
majorCode: string
majorName: string
hasCost: boolean
hasArea: boolean
amount: number | null
landArea: number | null
path: string[]
}
interface XmScaleState {
detailRows?: DetailRow[]
@ -151,60 +140,75 @@ const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] =>
}
const loadFromIndexedDB = async (gridApi: any) => {
const applyPinnedTotalAmount = (
api: GridApi<DetailRow> | null | undefined,
totalAmount: number | null | undefined
) => {
const normalized = typeof totalAmount === 'number' && Number.isFinite(totalAmount)
? roundTo(totalAmount, 2)
: null
pinnedTopRowData.value[0].amount = normalized
const pinnedTopNode = api?.getPinnedTopRow(0)
if (pinnedTopNode) {
pinnedTopNode.setDataValue('amount', normalized)
}
}
const loadFromIndexedDB = async (api: GridApi<DetailRow>) => {
try {
const [baseInfo, contractData] = await Promise.all([
localforage.getItem<XmBaseInfoState>(BASE_INFO_KEY),
localforage.getItem<XmScaleState>(props.dbKey)
])
const baseInfo = await localforage.getItem<XmBaseInfoState>(BASE_INFO_KEY)
activeIndustryId.value =
typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
if (!activeIndustryId.value) {
detailDict.value = []
return
}
const filteredEntries = majorEntries.filter(([id]) => isMajorIdInIndustryScope(id, activeIndustryId.value))
detailDict.value = buildDetailDict(filteredEntries)
if (!activeIndustryId.value) {
detailRows.value = []
roughCalcEnabled.value = false
applyPinnedTotalAmount(api, null)
return
}
const data = await localforage.getItem<XmScaleState>(props.dbKey)
const filteredEntries = majorEntries.filter(([id]) =>
isMajorIdInIndustryScope(id, activeIndustryId.value)
)
detailDict.value = buildDetailDict(filteredEntries)
roughCalcEnabled.value = data?.roughCalcEnabled || false
const pinnedTopNode = gridApi.getPinnedTopRow(0)
if (pinnedTopNode) {
pinnedTopNode.setDataValue('amount', data?.totalAmount || null)
}
//
if (data?.detailRows) {
detailRows.value = mergeWithDictRows(data.detailRows)
roughCalcEnabled.value = Boolean(contractData?.roughCalcEnabled)
applyPinnedTotalAmount(api, contractData?.totalAmount)
if (contractData?.detailRows) {
detailRows.value = mergeWithDictRows(contractData.detailRows)
return
}
if (props.xmInfoKey) {
// id
const xmData =
(await localforage.getItem<XmScaleState>(props.xmInfoKey))
roughCalcEnabled.value = xmData?.roughCalcEnabled || false
if (pinnedTopNode) {
pinnedTopNode.setDataValue('amount', xmData?.totalAmount || null)
}
const xmData = await localforage.getItem<XmScaleState>(props.xmInfoKey)
roughCalcEnabled.value = Boolean(xmData?.roughCalcEnabled)
applyPinnedTotalAmount(api, xmData?.totalAmount)
if (xmData?.detailRows) {
detailRows.value = mergeWithDictRows(xmData.detailRows)
detailRows.value = mergeWithDictRows(xmData.detailRows.map(e => ({...e,
amount: null,
landArea: null
})))
return
}
}
detailRows.value = buildDefaultRows()
saveToIndexedDB()
void saveToIndexedDB()
} catch (error) {
console.error('loadFromIndexedDB failed:', error)
activeIndustryId.value = ''
detailRows.value = []
roughCalcEnabled.value = false
applyPinnedTotalAmount(api, null)
}
}
@ -406,17 +410,19 @@ const setDetailRowsHidden = (hidden: boolean) => {
}
}
let oldValue:number|null
const onRoughCalcSwitch = (checked: boolean) => {
gridApi.value?.stopEditing(true)
roughCalcEnabled.value = checked
setDetailRowsHidden(checked)
if (!checked) {
oldValue=pinnedTopRowData.value[0].amount
syncPinnedTotalForNormalMode()
} else {
pinnedTopRowData.value[0].amount = null
pinnedTopRowData.value[0].amount = oldValue
const pinnedTopNode = gridApi.value?.getPinnedTopRow(0)
if (pinnedTopNode) {
pinnedTopNode.setDataValue('amount', null)
pinnedTopNode.setDataValue('amount', oldValue)
}
}
schedulePersist()

View File

@ -55,6 +55,8 @@ const CONTRACT_SEGMENT_FILE_EXTENSION = '.htzw'
const CONTRACT_SEGMENT_VERSION = 1
const CONTRACT_KEY_PREFIX = 'ht-info-v3-'
const SERVICE_KEY_PREFIX = 'zxFW-'
const CONTRACT_CONSULT_FACTOR_KEY_PREFIX = 'ht-consult-category-factor-v1-'
const CONTRACT_MAJOR_FACTOR_KEY_PREFIX = 'ht-major-factor-v1-'
const PRICING_KEY_PREFIXES = ['tzGMF-', 'ydGMF-', 'gzlF-', 'hourlyPricing-']
const PROJECT_INFO_KEY = 'xm-base-info-v1'
@ -383,6 +385,8 @@ const isContractSegmentPackage = (value: unknown): value is ContractSegmentPacka
const isContractRelatedForageKey = (key: string, contractId: string) => {
if (key === `${CONTRACT_KEY_PREFIX}${contractId}`) return true
if (key === `${SERVICE_KEY_PREFIX}${contractId}`) return true
if (key === `${CONTRACT_CONSULT_FACTOR_KEY_PREFIX}${contractId}`) return true
if (key === `${CONTRACT_MAJOR_FACTOR_KEY_PREFIX}${contractId}`) return true
if (PRICING_KEY_PREFIXES.some(prefix => key.startsWith(`${prefix}${contractId}-`))) return true
return false
}
@ -407,6 +411,12 @@ const readContractRelatedForageEntries = async (contractIds: string[]) => {
const rewriteKeyWithContractId = (key: string, fromId: string, toId: string) => {
if (key === `${CONTRACT_KEY_PREFIX}${fromId}`) return `${CONTRACT_KEY_PREFIX}${toId}`
if (key === `${SERVICE_KEY_PREFIX}${fromId}`) return `${SERVICE_KEY_PREFIX}${toId}`
if (key === `${CONTRACT_CONSULT_FACTOR_KEY_PREFIX}${fromId}`) {
return `${CONTRACT_CONSULT_FACTOR_KEY_PREFIX}${toId}`
}
if (key === `${CONTRACT_MAJOR_FACTOR_KEY_PREFIX}${fromId}`) {
return `${CONTRACT_MAJOR_FACTOR_KEY_PREFIX}${toId}`
}
for (const prefix of PRICING_KEY_PREFIXES) {
if (key.startsWith(`${prefix}${fromId}-`)) {
return key.replace(`${prefix}${fromId}-`, `${prefix}${toId}-`)

View File

@ -0,0 +1,19 @@
<script setup lang="ts">
import { serviceList } from '@/sql'
import XmFactorGrid from '@/components/common/XmFactorGrid.vue'
const props = defineProps<{
contractId: string
}>()
</script>
<template>
<XmFactorGrid
title="咨询分类系数明细"
:storage-key="`ht-consult-category-factor-v1-${props.contractId}`"
parent-storage-key="xm-consult-category-factor-v1"
:dict="serviceList"
:disable-budget-edit-when-standard-null="true"
:exclude-notshow-by-zxflxs="true"
/>
</template>

View File

@ -0,0 +1,64 @@
<script setup lang="ts">
import { computed, onActivated, onMounted, ref } from 'vue'
import localforage from 'localforage'
import { getMajorDictEntries, isMajorIdInIndustryScope } from '@/sql'
import XmFactorGrid from '@/components/common/XmFactorGrid.vue'
interface XmBaseInfoState {
projectIndustry?: string
}
type MajorItem = {
code: string
name: string
defCoe: number | null
desc?: string | null
notshowByzxflxs?: boolean
}
const props = defineProps<{
contractId: string
}>()
const PROJECT_INFO_KEY = 'xm-base-info-v1'
const projectIndustry = ref('')
const loadProjectIndustry = async () => {
try {
const data = await localforage.getItem<XmBaseInfoState>(PROJECT_INFO_KEY)
projectIndustry.value =
typeof data?.projectIndustry === 'string' ? data.projectIndustry.trim() : ''
} catch (error) {
console.error('loadProjectIndustry failed:', error)
projectIndustry.value = ''
}
}
const filteredMajorDict = computed<Record<string, MajorItem>>(() => {
const industry = projectIndustry.value
if (!industry) return {}
const entries = getMajorDictEntries()
.filter(({ id }) => isMajorIdInIndustryScope(id, industry))
.map(({ id, item }) => [id, item as MajorItem] as const)
return Object.fromEntries(entries)
})
onMounted(() => {
void loadProjectIndustry()
})
onActivated(() => {
void loadProjectIndustry()
})
</script>
<template>
<XmFactorGrid
title="工程专业系数明细"
:storage-key="`ht-major-factor-v1-${props.contractId}`"
parent-storage-key="xm-major-factor-v1"
:dict="filteredMajorDict"
:disable-budget-edit-when-standard-null="true"
:exclude-notshow-by-zxflxs="true"
/>
</template>

View File

@ -36,7 +36,7 @@ const clearAll = () => {
<label class="block text-[11px] font-medium text-foreground leading-none">选择服务</label>
<button
type="button"
class="h-6 rounded-md border px-2 text-[13px] text-muted-foreground transition hover:bg-accent"
class="cursor-pointer h-6 rounded-md border px-2 text-[13px] text-muted-foreground transition hover:bg-accent"
@click="clearAll"
>
清空

View File

@ -59,9 +59,42 @@ const zxfwView = markRaw(
})
);
const consultCategoryFactorView = markRaw(
defineComponent({
name: 'HtConsultCategoryFactorWithProps',
setup() {
const AsyncHtConsultCategoryFactor = defineAsyncComponent({
loader: () => import('@/components/views/HtConsultCategoryFactor.vue'),
onError: (err) => {
console.error('加载 HtConsultCategoryFactor 组件失败:', err);
}
});
return () => h(AsyncHtConsultCategoryFactor, { contractId: props.contractId });
}
})
);
const majorFactorView = markRaw(
defineComponent({
name: 'HtMajorFactorWithProps',
setup() {
const AsyncHtMajorFactor = defineAsyncComponent({
loader: () => import('@/components/views/HtMajorFactor.vue'),
onError: (err) => {
console.error('加载 HtMajorFactor 组件失败:', err);
}
});
return () => h(AsyncHtMajorFactor, { contractId: props.contractId });
}
})
);
// 4.
const xmCategories: XmCategoryItem[] = [
{ key: 'info', label: '规模信息', component: htView },
{ key: 'contract', label: '咨询服务', component: zxfwView }
{ key: 'consult-category-factor', label: '咨询分类系数', component: consultCategoryFactorView },
{ key: 'major-factor', label: '工程专业系数', component: majorFactorView },
{ key: 'contract', label: '咨询服务', component: zxfwView },
];
</script>

View File

@ -341,8 +341,8 @@ const userGuideSteps: UserGuideStep[] = [
]
const componentMap: Record<string, any> = {
XmView: markRaw(defineAsyncComponent(() => import('@/components/views/Xm.vue'))),
ContractDetailView: markRaw(defineAsyncComponent(() => import('@/components/views/ContractDetailView.vue'))),
XmView: markRaw(defineAsyncComponent(() => import('@/components/views/xmCard.vue'))),
ContractDetailView: markRaw(defineAsyncComponent(() => import('@/components/views/htCard.vue'))),
ZxFwView: markRaw(defineAsyncComponent(() => import('@/components/views/ZxFwView.vue'))),
}