fix
This commit is contained in:
parent
626513bc21
commit
c482faacbf
@ -35,6 +35,7 @@ type DictSource = Record<string, DictItem>
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
title: string
|
title: string
|
||||||
storageKey: string
|
storageKey: string
|
||||||
|
parentStorageKey?: string
|
||||||
dict: DictSource
|
dict: DictSource
|
||||||
disableBudgetEditWhenStandardNull?: boolean
|
disableBudgetEditWhenStandardNull?: boolean
|
||||||
excludeNotshowByZxflxs?: 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 () => {
|
const loadFromIndexedDB = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await localforage.getItem<GridState>(props.storageKey)
|
const data = await loadGridState(props.storageKey)
|
||||||
if (data?.detailRows) {
|
if (data) {
|
||||||
detailRows.value = mergeWithDictRows(data.detailRows)
|
detailRows.value = mergeWithDictRows(data.detailRows)
|
||||||
return
|
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()
|
detailRows.value = buildDefaultRows()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('loadFromIndexedDB failed:', error)
|
console.error('loadFromIndexedDB failed:', error)
|
||||||
|
|||||||
@ -28,18 +28,7 @@ interface DictGroup {
|
|||||||
children: DictLeaf[]
|
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 {
|
interface XmScaleState {
|
||||||
detailRows?: DetailRow[]
|
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 {
|
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 =
|
activeIndustryId.value =
|
||||||
typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
typeof baseInfo?.projectIndustry === 'string' ? baseInfo.projectIndustry.trim() : ''
|
||||||
|
|
||||||
if (!activeIndustryId.value) {
|
if (!activeIndustryId.value) {
|
||||||
detailDict.value = []
|
detailDict.value = []
|
||||||
return
|
|
||||||
}
|
|
||||||
const filteredEntries = majorEntries.filter(([id]) => isMajorIdInIndustryScope(id, activeIndustryId.value))
|
|
||||||
detailDict.value = buildDetailDict(filteredEntries)
|
|
||||||
if (!activeIndustryId.value) {
|
|
||||||
detailRows.value = []
|
detailRows.value = []
|
||||||
|
roughCalcEnabled.value = false
|
||||||
|
applyPinnedTotalAmount(api, null)
|
||||||
return
|
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
|
roughCalcEnabled.value = Boolean(contractData?.roughCalcEnabled)
|
||||||
const pinnedTopNode = gridApi.getPinnedTopRow(0)
|
applyPinnedTotalAmount(api, contractData?.totalAmount)
|
||||||
if (pinnedTopNode) {
|
if (contractData?.detailRows) {
|
||||||
pinnedTopNode.setDataValue('amount', data?.totalAmount || null)
|
detailRows.value = mergeWithDictRows(contractData.detailRows)
|
||||||
}
|
|
||||||
//
|
|
||||||
if (data?.detailRows) {
|
|
||||||
detailRows.value = mergeWithDictRows(data.detailRows)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.xmInfoKey) {
|
if (props.xmInfoKey) {
|
||||||
// 首次创建合同段时,默认继承项目规模信息(同一套专业字典,按 id 对齐)
|
// 首次创建合同段时,默认继承项目规模信息(同一套专业字典,按 id 对齐)
|
||||||
const xmData =
|
const xmData = await localforage.getItem<XmScaleState>(props.xmInfoKey)
|
||||||
(await localforage.getItem<XmScaleState>(props.xmInfoKey))
|
roughCalcEnabled.value = Boolean(xmData?.roughCalcEnabled)
|
||||||
roughCalcEnabled.value = xmData?.roughCalcEnabled || false
|
applyPinnedTotalAmount(api, xmData?.totalAmount)
|
||||||
if (pinnedTopNode) {
|
|
||||||
pinnedTopNode.setDataValue('amount', xmData?.totalAmount || null)
|
|
||||||
}
|
|
||||||
if (xmData?.detailRows) {
|
if (xmData?.detailRows) {
|
||||||
detailRows.value = mergeWithDictRows(xmData.detailRows)
|
|
||||||
|
detailRows.value = mergeWithDictRows(xmData.detailRows.map(e => ({...e,
|
||||||
|
amount: null,
|
||||||
|
landArea: null
|
||||||
|
})))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
detailRows.value = buildDefaultRows()
|
detailRows.value = buildDefaultRows()
|
||||||
|
|
||||||
saveToIndexedDB()
|
|
||||||
|
void saveToIndexedDB()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('loadFromIndexedDB failed:', error)
|
console.error('loadFromIndexedDB failed:', error)
|
||||||
activeIndustryId.value = ''
|
activeIndustryId.value = ''
|
||||||
detailRows.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) => {
|
const onRoughCalcSwitch = (checked: boolean) => {
|
||||||
gridApi.value?.stopEditing(true)
|
gridApi.value?.stopEditing(true)
|
||||||
roughCalcEnabled.value = checked
|
roughCalcEnabled.value = checked
|
||||||
setDetailRowsHidden(checked)
|
setDetailRowsHidden(checked)
|
||||||
if (!checked) {
|
if (!checked) {
|
||||||
|
oldValue=pinnedTopRowData.value[0].amount
|
||||||
syncPinnedTotalForNormalMode()
|
syncPinnedTotalForNormalMode()
|
||||||
} else {
|
} else {
|
||||||
pinnedTopRowData.value[0].amount = null
|
pinnedTopRowData.value[0].amount = oldValue
|
||||||
const pinnedTopNode = gridApi.value?.getPinnedTopRow(0)
|
const pinnedTopNode = gridApi.value?.getPinnedTopRow(0)
|
||||||
if (pinnedTopNode) {
|
if (pinnedTopNode) {
|
||||||
pinnedTopNode.setDataValue('amount', null)
|
pinnedTopNode.setDataValue('amount', oldValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
schedulePersist()
|
schedulePersist()
|
||||||
|
|||||||
@ -55,6 +55,8 @@ const CONTRACT_SEGMENT_FILE_EXTENSION = '.htzw'
|
|||||||
const CONTRACT_SEGMENT_VERSION = 1
|
const CONTRACT_SEGMENT_VERSION = 1
|
||||||
const CONTRACT_KEY_PREFIX = 'ht-info-v3-'
|
const CONTRACT_KEY_PREFIX = 'ht-info-v3-'
|
||||||
const SERVICE_KEY_PREFIX = 'zxFW-'
|
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 PRICING_KEY_PREFIXES = ['tzGMF-', 'ydGMF-', 'gzlF-', 'hourlyPricing-']
|
||||||
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
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) => {
|
const isContractRelatedForageKey = (key: string, contractId: string) => {
|
||||||
if (key === `${CONTRACT_KEY_PREFIX}${contractId}`) return true
|
if (key === `${CONTRACT_KEY_PREFIX}${contractId}`) return true
|
||||||
if (key === `${SERVICE_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
|
if (PRICING_KEY_PREFIXES.some(prefix => key.startsWith(`${prefix}${contractId}-`))) return true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -407,6 +411,12 @@ const readContractRelatedForageEntries = async (contractIds: string[]) => {
|
|||||||
const rewriteKeyWithContractId = (key: string, fromId: string, toId: string) => {
|
const rewriteKeyWithContractId = (key: string, fromId: string, toId: string) => {
|
||||||
if (key === `${CONTRACT_KEY_PREFIX}${fromId}`) return `${CONTRACT_KEY_PREFIX}${toId}`
|
if (key === `${CONTRACT_KEY_PREFIX}${fromId}`) return `${CONTRACT_KEY_PREFIX}${toId}`
|
||||||
if (key === `${SERVICE_KEY_PREFIX}${fromId}`) return `${SERVICE_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) {
|
for (const prefix of PRICING_KEY_PREFIXES) {
|
||||||
if (key.startsWith(`${prefix}${fromId}-`)) {
|
if (key.startsWith(`${prefix}${fromId}-`)) {
|
||||||
return key.replace(`${prefix}${fromId}-`, `${prefix}${toId}-`)
|
return key.replace(`${prefix}${fromId}-`, `${prefix}${toId}-`)
|
||||||
|
|||||||
19
src/components/views/HtConsultCategoryFactor.vue
Normal file
19
src/components/views/HtConsultCategoryFactor.vue
Normal 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>
|
||||||
64
src/components/views/HtMajorFactor.vue
Normal file
64
src/components/views/HtMajorFactor.vue
Normal 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>
|
||||||
@ -36,7 +36,7 @@ const clearAll = () => {
|
|||||||
<label class="block text-[11px] font-medium text-foreground leading-none">选择服务</label>
|
<label class="block text-[11px] font-medium text-foreground leading-none">选择服务</label>
|
||||||
<button
|
<button
|
||||||
type="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"
|
@click="clearAll"
|
||||||
>
|
>
|
||||||
清空
|
清空
|
||||||
|
|||||||
@ -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. 给分类数组添加严格类型标注
|
// 4. 给分类数组添加严格类型标注
|
||||||
const xmCategories: XmCategoryItem[] = [
|
const xmCategories: XmCategoryItem[] = [
|
||||||
{ key: 'info', label: '规模信息', component: htView },
|
{ 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>
|
</script>
|
||||||
@ -341,8 +341,8 @@ const userGuideSteps: UserGuideStep[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const componentMap: Record<string, any> = {
|
const componentMap: Record<string, any> = {
|
||||||
XmView: markRaw(defineAsyncComponent(() => import('@/components/views/Xm.vue'))),
|
XmView: markRaw(defineAsyncComponent(() => import('@/components/views/xmCard.vue'))),
|
||||||
ContractDetailView: markRaw(defineAsyncComponent(() => import('@/components/views/ContractDetailView.vue'))),
|
ContractDetailView: markRaw(defineAsyncComponent(() => import('@/components/views/htCard.vue'))),
|
||||||
ZxFwView: markRaw(defineAsyncComponent(() => import('@/components/views/ZxFwView.vue'))),
|
ZxFwView: markRaw(defineAsyncComponent(() => import('@/components/views/ZxFwView.vue'))),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user