修复大量问题
This commit is contained in:
parent
8e5cb49da5
commit
59bab33e9b
@ -196,7 +196,7 @@ let data1 = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
tasks: [{ serviceid: 0, text: ['abc', 'efg'] },
|
tasks: [{ serviceid: 0, text: ['abc', 'efg'] },
|
||||||
{ serviceid: 2,text: ['abc', 'efg'] } //tasks不分组的时候传单对象[{text: ['abc', 'efg']}],分组的时候传分组的serviceid
|
{ serviceid: 2,text: ['abc', 'efg'] } //tasks不分组的时候传单对象[{text: ['abc', 'efg']}],分组的时候传分组的服务id[{ serviceid: 0, text: ['abc', 'efg'] },...]
|
||||||
],// 工作内容
|
],// 工作内容
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@ -3,13 +3,14 @@ import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
|||||||
import {
|
import {
|
||||||
useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||||
|
|
||||||
interface HtBaseInfoState {
|
interface HtBaseInfoState {
|
||||||
quality: string
|
quality: string
|
||||||
duration:
|
duration:
|
||||||
string
|
string
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_QUALITY = '造价咨询服务的综合评价应达到"较好"或综合评分90分'
|
const DEFAULT_QUALITY = '造价咨询服务的综合评价应达到"较好"或综合评分90分'
|
||||||
|
const DEFAULT_DURATION = ''
|
||||||
|
|
||||||
const props =
|
const props =
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@ -43,13 +44,21 @@ import {
|
|||||||
const loadForm = async () => {
|
const loadForm = async () => {
|
||||||
const data = await
|
const data = await
|
||||||
zxFwPricingStore.loadKeyState<HtBaseInfoState>(storageKey())
|
zxFwPricingStore.loadKeyState<HtBaseInfoState>(storageKey())
|
||||||
|
const hasStoredValue = Boolean(
|
||||||
|
data &&
|
||||||
|
(Object.prototype.hasOwnProperty.call(data, 'quality') || Object.prototype.hasOwnProperty.call(data, 'duration'))
|
||||||
|
)
|
||||||
quality.value = typeof data?.quality === 'string' &&
|
quality.value = typeof data?.quality === 'string' &&
|
||||||
data.quality ? data.quality : DEFAULT_QUALITY
|
data.quality ? data.quality : DEFAULT_QUALITY
|
||||||
duration.value = typeof data?.duration === 'string' ? data.duration :
|
duration.value = typeof data?.duration === 'string'
|
||||||
''
|
? data.duration
|
||||||
|
: (hasStoredValue ? '' : DEFAULT_DURATION)
|
||||||
const payload: HtBaseInfoState = { quality: quality.value, duration: duration.value }
|
const payload: HtBaseInfoState = { quality: quality.value, duration: duration.value }
|
||||||
|
|
||||||
lastSavedSnapshot.value = JSON.stringify(payload)
|
lastSavedSnapshot.value = JSON.stringify(payload)
|
||||||
|
if (!hasStoredValue) {
|
||||||
|
saveForm(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch([quality, duration], () => { saveForm()
|
watch([quality, duration], () => { saveForm()
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
:subtitle="`合同段ID:${contractId}`"
|
:subtitle="`合同段ID:${contractId}`"
|
||||||
:meta-text="`合同段预算金额:${formatBudgetAmount(contractBudget)}`"
|
:meta-text="`合同段预算金额:${formatBudgetAmount(contractBudget)}`"
|
||||||
:copy-text="contractId"
|
:copy-text="contractId"
|
||||||
:storage-key="`project-active-cat-${contractId}`"
|
:storage-key="typeLineStorageKey"
|
||||||
default-category="info"
|
default-category="base-info"
|
||||||
:categories="xmCategories"
|
:categories="xmCategories"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -61,6 +61,7 @@ interface QuantityMethodStateLike {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const contractBudget = ref<number | null>(null)
|
const contractBudget = ref<number | null>(null)
|
||||||
|
const typeLineStorageKey = computed(() => `project-active-cat-${props.contractId}`)
|
||||||
let budgetRefreshTimer: ReturnType<typeof setTimeout> | null = null
|
let budgetRefreshTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
const toFiniteNumber = (value: unknown): number | null => {
|
const toFiniteNumber = (value: unknown): number | null => {
|
||||||
|
|||||||
@ -356,7 +356,7 @@ const mergeWithStoredRows = (rowsFromDb: unknown): FeeMethodRow[] => {
|
|||||||
return fixedNames.value.map((item, index) => {
|
return fixedNames.value.map((item, index) => {
|
||||||
const fromDb = byName.get(item.name)
|
const fromDb = byName.get(item.name)
|
||||||
return {
|
return {
|
||||||
id: item?.id || `fee-method-fixed-${index}`,
|
id: String(item?.id || `fee-method-fixed-${index}`),
|
||||||
name:item.name,
|
name:item.name,
|
||||||
rateFee: fromDb?.rateFee ?? null,
|
rateFee: fromDb?.rateFee ?? null,
|
||||||
hourlyFee: fromDb?.hourlyFee ?? null,
|
hourlyFee: fromDb?.hourlyFee ?? null,
|
||||||
@ -424,7 +424,6 @@ const clearRow = async (id: string) => {
|
|||||||
const editRow = (id: string) => {
|
const editRow = (id: string) => {
|
||||||
const row = detailRows.value.find(item => item.id === id)
|
const row = detailRows.value.find(item => item.id === id)
|
||||||
if (!row) return
|
if (!row) return
|
||||||
console.log(id)
|
|
||||||
tabStore.openTab({
|
tabStore.openTab({
|
||||||
id: `ht-fee-edit-${props.storageKey}-${id}`,
|
id: `ht-fee-edit-${props.storageKey}-${id}`,
|
||||||
title: `费用编辑-${row.name || '未命名'}`,
|
title: `费用编辑-${row.name || '未命名'}`,
|
||||||
@ -454,7 +453,7 @@ const ActionCellRenderer = defineComponent({
|
|||||||
const onActionClick = (action: 'edit' | 'clear') => (event: MouseEvent) => {
|
const onActionClick = (action: 'edit' | 'clear') => (event: MouseEvent) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
const rowId = props.params.data?.id
|
const rowId = String(props.params.data?.id || '').trim()
|
||||||
if (!rowId) return
|
if (!rowId) return
|
||||||
if (action === 'edit') {
|
if (action === 'edit') {
|
||||||
props.params.context?.onActionEdit?.(rowId)
|
props.params.context?.onActionEdit?.(rowId)
|
||||||
|
|||||||
@ -34,6 +34,7 @@ interface WorkContentRow {
|
|||||||
content: string
|
content: string
|
||||||
type: WorkType
|
type: WorkType
|
||||||
serviceGroup?: string
|
serviceGroup?: string
|
||||||
|
serviceid?: number | null
|
||||||
remark: string
|
remark: string
|
||||||
checked: boolean
|
checked: boolean
|
||||||
custom: boolean
|
custom: boolean
|
||||||
@ -93,6 +94,12 @@ const syncGroupedRowsRender = async () => {
|
|||||||
}, 16)
|
}, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toServiceId = (value: unknown): number | null => {
|
||||||
|
const parsed = Number(value)
|
||||||
|
if (!Number.isSafeInteger(parsed)) return null
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const loadProjectIndustryId = async () => {
|
const loadProjectIndustryId = async () => {
|
||||||
@ -193,6 +200,7 @@ const buildDefaultRowsFromDict = async (): Promise<WorkContentRow[]> => {
|
|||||||
content,
|
content,
|
||||||
type: typeLabel,
|
type: typeLabel,
|
||||||
serviceGroup,
|
serviceGroup,
|
||||||
|
serviceid: toServiceId(entry.serviceid),
|
||||||
remark: '',
|
remark: '',
|
||||||
checked: false,
|
checked: false,
|
||||||
custom: false,
|
custom: false,
|
||||||
@ -247,9 +255,27 @@ const loadFromStore = async () => {
|
|||||||
const persistedRows = state.detailRows.map(item => ({
|
const persistedRows = state.detailRows.map(item => ({
|
||||||
...item,
|
...item,
|
||||||
type: item.custom ? '自定义' : (item.type || '基本工作'),
|
type: item.custom ? '自定义' : (item.type || '基本工作'),
|
||||||
|
serviceid: toServiceId(item.serviceid),
|
||||||
path: Array.isArray(item.path) && item.path.length ? item.path : ['自定义', item.content || '未命名']
|
path: Array.isArray(item.path) && item.path.length ? item.path : ['自定义', item.content || '未命名']
|
||||||
})) as WorkContentRow[]
|
})) as WorkContentRow[]
|
||||||
|
|
||||||
|
const defaultGroupServiceIdMap = new Map<string, number>()
|
||||||
|
for (const row of defaultRows) {
|
||||||
|
const groupName = String(row.serviceGroup || '').trim()
|
||||||
|
const serviceid = toServiceId(row.serviceid)
|
||||||
|
if (!groupName || serviceid == null) continue
|
||||||
|
defaultGroupServiceIdMap.set(groupName, serviceid)
|
||||||
|
}
|
||||||
|
for (const row of persistedRows) {
|
||||||
|
if (row.serviceid != null) continue
|
||||||
|
const groupName = String(row.serviceGroup || '').trim()
|
||||||
|
if (!groupName) continue
|
||||||
|
const fallbackServiceId = defaultGroupServiceIdMap.get(groupName)
|
||||||
|
if (fallbackServiceId != null) {
|
||||||
|
row.serviceid = fallbackServiceId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 按最新词典规则重建默认行,再合并历史勾选/备注,保证分组规则变更后立即生效。
|
// 按最新词典规则重建默认行,再合并历史勾选/备注,保证分组规则变更后立即生效。
|
||||||
if (defaultRows.length > 0) {
|
if (defaultRows.length > 0) {
|
||||||
const persistedCustomRows = persistedRows.filter(item => item.custom)
|
const persistedCustomRows = persistedRows.filter(item => item.custom)
|
||||||
@ -462,6 +488,7 @@ const createAddTriggerRow = (groupName?: string): WorkContentRow => {
|
|||||||
content: '点击添加自定义内容',
|
content: '点击添加自定义内容',
|
||||||
type: '自定义' as WorkType,
|
type: '自定义' as WorkType,
|
||||||
serviceGroup: groupName || '',
|
serviceGroup: groupName || '',
|
||||||
|
serviceid: null,
|
||||||
remark: '',
|
remark: '',
|
||||||
checked: false,
|
checked: false,
|
||||||
custom: false,
|
custom: false,
|
||||||
@ -513,11 +540,19 @@ const addCustomRow = (groupName?: string) => {
|
|||||||
const finalGroupName = isWholeProcessGroupedMode.value
|
const finalGroupName = isWholeProcessGroupedMode.value
|
||||||
? String(groupName || groupedServiceGroups.value[0] || '').trim()
|
? String(groupName || groupedServiceGroups.value[0] || '').trim()
|
||||||
: ''
|
: ''
|
||||||
|
const finalServiceId = isWholeProcessGroupedMode.value
|
||||||
|
? (() => {
|
||||||
|
const pureRows = getPersistableRows(rowData.value)
|
||||||
|
const hit = pureRows.find(item => String(item.serviceGroup || '').trim() === finalGroupName && item.serviceid != null)
|
||||||
|
return hit?.serviceid ?? null
|
||||||
|
})()
|
||||||
|
: null
|
||||||
const nextRow: WorkContentRow = {
|
const nextRow: WorkContentRow = {
|
||||||
id: `custom-${ts}`,
|
id: `custom-${ts}`,
|
||||||
content: '',
|
content: '',
|
||||||
type: '自定义' as WorkType,
|
type: '自定义' as WorkType,
|
||||||
serviceGroup: finalGroupName,
|
serviceGroup: finalGroupName,
|
||||||
|
serviceid: finalServiceId,
|
||||||
remark: '',
|
remark: '',
|
||||||
checked: false,
|
checked: false,
|
||||||
custom: true,
|
custom: true,
|
||||||
|
|||||||
@ -26,19 +26,20 @@ interface TypeLineCategoryItem {
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
sourceTitle?: string
|
sourceTitle?: string
|
||||||
storageKey: string
|
storageKey: string
|
||||||
rowId: string
|
rowId: string | number
|
||||||
rowName?: string
|
rowName?: string
|
||||||
contractId?: string
|
contractId?: string
|
||||||
contractName?: string
|
contractName?: string
|
||||||
}>()
|
}>()
|
||||||
const sourceTitleText = computed(() => props.sourceTitle || '费用明细')
|
const sourceTitleText = computed(() => props.sourceTitle || '费用明细')
|
||||||
const rowNameText = computed(() => props.rowName || '未命名')
|
const rowNameText = computed(() => props.rowName || '未命名')
|
||||||
|
const rowIdText = computed(() => String(props.rowId || '').trim())
|
||||||
const contractIdText = computed(() => String(props.contractId || '').trim())
|
const contractIdText = computed(() => String(props.contractId || '').trim())
|
||||||
const contractNameText = computed(() => String(props.contractName || '').trim() || contractIdText.value || '-')
|
const contractNameText = computed(() => String(props.contractName || '').trim() || contractIdText.value || '-')
|
||||||
const titleText = computed(() => `合同段:${contractNameText.value} · ${rowNameText.value || sourceTitleText.value}`)
|
const titleText = computed(() => `合同段:${contractNameText.value} · ${rowNameText.value || sourceTitleText.value}`)
|
||||||
const activeTypeStorageKey = computed(() => `ht-fee-type-active-cat-${props.storageKey}-${props.rowId}`)
|
const activeTypeStorageKey = computed(() => `ht-fee-type-active-cat-${props.storageKey}-${rowIdText.value}`)
|
||||||
const buildMethodStorageKey = (method: 'rate-fee' | 'hourly-fee' | 'quantity-unit-price-fee') =>
|
const buildMethodStorageKey = (method: 'rate-fee' | 'hourly-fee' | 'quantity-unit-price-fee') =>
|
||||||
`${props.storageKey}-${props.rowId}-${method}`
|
`${props.storageKey}-${rowIdText.value}-${method}`
|
||||||
|
|
||||||
const quantityUnitPricePane = markRaw(
|
const quantityUnitPricePane = markRaw(
|
||||||
defineComponent({
|
defineComponent({
|
||||||
@ -50,7 +51,7 @@ const quantityUnitPricePane = markRaw(
|
|||||||
title: '数量单价',
|
title: '数量单价',
|
||||||
storageKey: quantityStorageKey.value,
|
storageKey: quantityStorageKey.value,
|
||||||
htMainStorageKey: props.storageKey,
|
htMainStorageKey: props.storageKey,
|
||||||
htRowId: props.rowId,
|
htRowId: rowIdText.value,
|
||||||
htMethodType: 'quantity-unit-price-fee'
|
htMethodType: 'quantity-unit-price-fee'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -67,7 +68,7 @@ const rateFeePane = markRaw(
|
|||||||
storageKey: rateStorageKey.value,
|
storageKey: rateStorageKey.value,
|
||||||
contractId: props.contractId,
|
contractId: props.contractId,
|
||||||
htMainStorageKey: props.storageKey,
|
htMainStorageKey: props.storageKey,
|
||||||
htRowId: props.rowId,
|
htRowId: rowIdText.value,
|
||||||
htMethodType: 'rate-fee'
|
htMethodType: 'rate-fee'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -84,7 +85,7 @@ const hourlyFeePane = markRaw(
|
|||||||
title: '工时法明细',
|
title: '工时法明细',
|
||||||
storageKey: hourlyStorageKey.value,
|
storageKey: hourlyStorageKey.value,
|
||||||
htMainStorageKey: props.storageKey,
|
htMainStorageKey: props.storageKey,
|
||||||
htRowId: props.rowId,
|
htRowId: rowIdText.value,
|
||||||
htMethodType: 'hourly-fee'
|
htMethodType: 'hourly-fee'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -109,7 +110,7 @@ const workContentPane = markRaw(
|
|||||||
})
|
})
|
||||||
return () => h(AsyncWorkContentGrid, {
|
return () => h(AsyncWorkContentGrid, {
|
||||||
title: '工作内容',
|
title: '工作内容',
|
||||||
storageKey: `work-content-${props.storageKey}-${props.rowId}`,
|
storageKey: `work-content-${props.storageKey}-${rowIdText.value}`,
|
||||||
dictMode: 'additional'
|
dictMode: 'additional'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import {
|
|||||||
import { decodeZwArchive, encodeZwArchive, ZW_FILE_EXTENSION } from '@/lib/zwArchive'
|
import { decodeZwArchive, encodeZwArchive, ZW_FILE_EXTENSION } from '@/lib/zwArchive'
|
||||||
import { PROJECT_TAB_ID, QUICK_TAB_ID, readWorkspaceMode, writeWorkspaceMode } from '@/lib/workspace'
|
import { PROJECT_TAB_ID, QUICK_TAB_ID, readWorkspaceMode, writeWorkspaceMode } from '@/lib/workspace'
|
||||||
import { addNumbers, roundTo } from '@/lib/decimal'
|
import { addNumbers, roundTo } from '@/lib/decimal'
|
||||||
|
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||||
import { exportFile, serviceList } from '@/sql'
|
import { exportFile, serviceList } from '@/sql'
|
||||||
|
|
||||||
interface DataEntry {
|
interface DataEntry {
|
||||||
@ -110,6 +111,7 @@ interface WorkContentRowLike {
|
|||||||
checked?: unknown
|
checked?: unknown
|
||||||
custom?: unknown
|
custom?: unknown
|
||||||
serviceGroup?: unknown
|
serviceGroup?: unknown
|
||||||
|
serviceid?: unknown
|
||||||
isAddTrigger?: unknown
|
isAddTrigger?: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,14 +546,11 @@ const finishReportExportProgress = (success: boolean, text: string, blobUrl?: st
|
|||||||
reportExportStatus.value = success ? 'success' : 'error'
|
reportExportStatus.value = success ? 'success' : 'error'
|
||||||
reportExportProgress.value = 100
|
reportExportProgress.value = 100
|
||||||
reportExportText.value = text
|
reportExportText.value = text
|
||||||
console.log(blobUrl)
|
|
||||||
reportExportBlobUrl.value = success && blobUrl ? blobUrl : null
|
reportExportBlobUrl.value = success && blobUrl ? blobUrl : null
|
||||||
reportExportToastOpen.value = true
|
reportExportToastOpen.value = true
|
||||||
if (!success || !blobUrl) {
|
|
||||||
reportExportToastTimer = setTimeout(() => {
|
reportExportToastTimer = setTimeout(() => {
|
||||||
reportExportToastOpen.value = false
|
reportExportToastOpen.value = false
|
||||||
}, success ? 1200 : 1800)
|
}, success ? 2000 : 1800)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const openExportedReport = () => {
|
const openExportedReport = () => {
|
||||||
@ -1037,6 +1036,7 @@ const sumNumbers = (values: Array<number | null | undefined>): number =>
|
|||||||
(sum, value) => sum + (typeof value === 'number' && Number.isFinite(value) ? value : 0),
|
(sum, value) => sum + (typeof value === 'number' && Number.isFinite(value) ? value : 0),
|
||||||
0
|
0
|
||||||
)
|
)
|
||||||
|
const toMoney = (value: unknown): number => roundTo(toFiniteNumber(value) ?? 0, 2)
|
||||||
|
|
||||||
const isNonEmptyString = (value: unknown): value is string =>
|
const isNonEmptyString = (value: unknown): value is string =>
|
||||||
typeof value === 'string' && value.trim().length > 0
|
typeof value === 'string' && value.trim().length > 0
|
||||||
@ -1100,10 +1100,42 @@ const toScaleProNum = (row: ScaleMethodRowLike): number => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const normalizeTaskText = (value: unknown): string => String(value || '').trim()
|
const normalizeTaskText = (value: unknown): string => String(value || '').trim()
|
||||||
|
const resolveTaskRowServiceId = (row: WorkContentRowLike): number | null =>
|
||||||
|
toSafeInteger((row as { serviceid?: unknown })?.serviceid)
|
||||||
|
const resolveScaleMethodFee = (row: ScaleMethodRowLike, mode: 'cost' | 'area') => {
|
||||||
|
const scaleValue = mode === 'cost' ? toFiniteNumber(row.amount) : toFiniteNumber(row.landArea)
|
||||||
|
const benchmarkSplit = getBenchmarkBudgetSplitByScale(scaleValue, mode)
|
||||||
|
const computedSplit = benchmarkSplit
|
||||||
|
? getScaleBudgetFeeSplit({
|
||||||
|
benchmarkBudgetBasic: benchmarkSplit.basic,
|
||||||
|
benchmarkBudgetOptional: benchmarkSplit.optional,
|
||||||
|
majorFactor: row.majorFactor,
|
||||||
|
consultCategoryFactor: row.consultCategoryFactor,
|
||||||
|
workStageFactor: row.workStageFactor,
|
||||||
|
workRatio: row.workRatio
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
const basicFee = toFiniteNumber(row.budgetFee) ?? computedSplit?.total ?? null
|
||||||
|
const basicFeeBasic = toFiniteNumber(row.budgetFeeBasic) ?? computedSplit?.basic ?? null
|
||||||
|
const basicFeeOptional = toFiniteNumber(row.budgetFeeOptional) ?? computedSplit?.optional ?? null
|
||||||
|
const basicFormula = typeof row.basicFormula === 'string' && row.basicFormula.trim()
|
||||||
|
? row.basicFormula
|
||||||
|
: (benchmarkSplit?.basicFormula ?? '')
|
||||||
|
const optionalFormula = typeof row.optionalFormula === 'string' && row.optionalFormula.trim()
|
||||||
|
? row.optionalFormula
|
||||||
|
: (benchmarkSplit?.optionalFormula ?? '')
|
||||||
|
return {
|
||||||
|
basicFee,
|
||||||
|
basicFeeBasic,
|
||||||
|
basicFeeOptional,
|
||||||
|
basicFormula,
|
||||||
|
optionalFormula
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const groupWorkContentTasks = (
|
const groupWorkContentTasks = (
|
||||||
rows: WorkContentRowLike[] | undefined,
|
rows: WorkContentRowLike[] | undefined,
|
||||||
options?: { forceUngroup?: boolean; serviceLabelToId?: Map<string, number> }
|
options?: { forceUngroup?: boolean }
|
||||||
): ExportTaskGroup[] => {
|
): ExportTaskGroup[] => {
|
||||||
const source = Array.isArray(rows) ? rows : []
|
const source = Array.isArray(rows) ? rows : []
|
||||||
const selected = source.filter(item => {
|
const selected = source.filter(item => {
|
||||||
@ -1114,7 +1146,7 @@ const groupWorkContentTasks = (
|
|||||||
})
|
})
|
||||||
if (selected.length === 0) return []
|
if (selected.length === 0) return []
|
||||||
|
|
||||||
const hasGroup = !options?.forceUngroup && selected.some(item => normalizeTaskText(item?.serviceGroup).length > 0)
|
const hasGroup = !options?.forceUngroup && selected.some(item => resolveTaskRowServiceId(item) != null)
|
||||||
if (!hasGroup) {
|
if (!hasGroup) {
|
||||||
const text = selected
|
const text = selected
|
||||||
.map(item => normalizeTaskText(item?.content))
|
.map(item => normalizeTaskText(item?.content))
|
||||||
@ -1122,31 +1154,35 @@ const groupWorkContentTasks = (
|
|||||||
return text.length > 0 ? [{ text }] : []
|
return text.length > 0 ? [{ text }] : []
|
||||||
}
|
}
|
||||||
|
|
||||||
const grouped = new Map<string, string[]>()
|
const grouped = new Map<number, string[]>()
|
||||||
const orderedGroupKeys: string[] = []
|
const orderedServiceIds: number[] = []
|
||||||
|
const ungroupedText: string[] = []
|
||||||
for (const item of selected) {
|
for (const item of selected) {
|
||||||
const groupName = normalizeTaskText(item?.serviceGroup)
|
|
||||||
const key = groupName || '__ungrouped__'
|
|
||||||
if (!grouped.has(key)) {
|
|
||||||
grouped.set(key, [])
|
|
||||||
orderedGroupKeys.push(key)
|
|
||||||
}
|
|
||||||
const content = normalizeTaskText(item?.content)
|
const content = normalizeTaskText(item?.content)
|
||||||
if (!content) continue
|
if (!content) continue
|
||||||
grouped.get(key)?.push(content)
|
const serviceid = resolveTaskRowServiceId(item)
|
||||||
|
if (serviceid == null) {
|
||||||
|
ungroupedText.push(content)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!grouped.has(serviceid)) {
|
||||||
|
grouped.set(serviceid, [])
|
||||||
|
orderedServiceIds.push(serviceid)
|
||||||
|
}
|
||||||
|
grouped.get(serviceid)?.push(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
const byLabel = options?.serviceLabelToId || new Map<string, number>()
|
const groupedTasks: ExportTaskGroup[] = []
|
||||||
return orderedGroupKeys
|
for (const serviceid of orderedServiceIds) {
|
||||||
.map(groupName => {
|
const text = grouped.get(serviceid) || []
|
||||||
const text = grouped.get(groupName) || []
|
if (text.length === 0) continue
|
||||||
if (text.length === 0) return null
|
groupedTasks.push({ serviceid, text })
|
||||||
const entry: ExportTaskGroup = { text }
|
}
|
||||||
const resolvedServiceId = byLabel.get(groupName)
|
|
||||||
if (resolvedServiceId != null) entry.serviceid = resolvedServiceId
|
if (ungroupedText.length > 0) {
|
||||||
return entry
|
groupedTasks.push({ text: ungroupedText })
|
||||||
})
|
}
|
||||||
.filter((item): item is ExportTaskGroup => Boolean(item))
|
return groupedTasks
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildProjectServiceCoes = (rows: FactorRowLike[] | undefined): ExportServiceCoe[] => {
|
const buildProjectServiceCoes = (rows: FactorRowLike[] | undefined): ExportServiceCoe[] => {
|
||||||
@ -1205,14 +1241,15 @@ const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod1 | n
|
|||||||
const det = rows
|
const det = rows
|
||||||
.map(row => {
|
.map(row => {
|
||||||
const major = toScaleMajorId(row)
|
const major = toScaleMajorId(row)
|
||||||
if (major == null || row.budgetFee == null) return null
|
if (major == null) return null
|
||||||
const proNum = toScaleProNum(row)
|
const proNum = toScaleProNum(row)
|
||||||
proSet.add(proNum)
|
proSet.add(proNum)
|
||||||
const cost = toFiniteNumber(row.amount)
|
const cost = toFiniteNumber(row.amount)
|
||||||
const basicFee = toFiniteNumber(row.budgetFee)
|
const feeResolved = resolveScaleMethodFee(row, 'cost')
|
||||||
|
const basicFee = feeResolved.basicFee
|
||||||
if (basicFee != null) hasTotalValue = true
|
if (basicFee != null) hasTotalValue = true
|
||||||
const basicFeeBasic = toFiniteNumber(row.budgetFeeBasic)
|
const basicFeeBasic = feeResolved.basicFeeBasic
|
||||||
const basicFeeOptional = toFiniteNumber(row.budgetFeeOptional)
|
const basicFeeOptional = feeResolved.basicFeeOptional
|
||||||
const remark = typeof row.remark === 'string' ? row.remark : ''
|
const remark = typeof row.remark === 'string' ? row.remark : ''
|
||||||
const hasValue =
|
const hasValue =
|
||||||
cost != null ||
|
cost != null ||
|
||||||
@ -1225,16 +1262,16 @@ const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod1 | n
|
|||||||
proNum,
|
proNum,
|
||||||
major,
|
major,
|
||||||
cost: cost ?? 0,
|
cost: cost ?? 0,
|
||||||
basicFee: basicFee ?? 0,
|
basicFee: toMoney(basicFee),
|
||||||
basicFormula: typeof row.basicFormula === 'string' ? row.basicFormula : '',
|
basicFormula: feeResolved.basicFormula,
|
||||||
basicFee_basic: basicFeeBasic ?? 0,
|
basicFee_basic: toMoney(basicFeeBasic),
|
||||||
optionalFormula: typeof row.optionalFormula === 'string' ? row.optionalFormula : '',
|
optionalFormula: feeResolved.optionalFormula,
|
||||||
basicFee_optional: basicFeeOptional ?? 0,
|
basicFee_optional: toMoney(basicFeeOptional),
|
||||||
serviceCoe: toFiniteNumberOrZero(row.consultCategoryFactor),
|
serviceCoe: toFiniteNumberOrZero(row.consultCategoryFactor),
|
||||||
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
||||||
processCoe: toFiniteNumber(row.workStageFactor) ?? 1,
|
processCoe: toFiniteNumber(row.workStageFactor) ?? 1,
|
||||||
proportion: toFiniteNumber(row.workRatio) ?? 1,
|
proportion: toFiniteNumber(row.workRatio) ?? 1,
|
||||||
fee: basicFee ?? 0,
|
fee: toMoney(basicFee),
|
||||||
remark
|
remark
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1244,10 +1281,10 @@ const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod1 | n
|
|||||||
return {
|
return {
|
||||||
proAmount: proSet.size > 0 ? proSet.size : 1,
|
proAmount: proSet.size > 0 ? proSet.size : 1,
|
||||||
cost: sumNumbers(det.map(item => item.cost)),
|
cost: sumNumbers(det.map(item => item.cost)),
|
||||||
basicFee: sumNumbers(det.map(item => item.basicFee)),
|
basicFee: toMoney(sumNumbers(det.map(item => item.basicFee))),
|
||||||
basicFee_basic: sumNumbers(det.map(item => item.basicFee_basic)),
|
basicFee_basic: toMoney(sumNumbers(det.map(item => item.basicFee_basic))),
|
||||||
basicFee_optional: sumNumbers(det.map(item => item.basicFee_optional)),
|
basicFee_optional: toMoney(sumNumbers(det.map(item => item.basicFee_optional))),
|
||||||
fee: sumNumbers(det.map(item => item.fee)),
|
fee: toMoney(sumNumbers(det.map(item => item.fee))),
|
||||||
det
|
det
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1259,14 +1296,15 @@ const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod2 | n
|
|||||||
const det = rows
|
const det = rows
|
||||||
.map(row => {
|
.map(row => {
|
||||||
const major = toScaleMajorId(row)
|
const major = toScaleMajorId(row)
|
||||||
if (major == null || row.budgetFee == null) return null
|
if (major == null) return null
|
||||||
const proNum = toScaleProNum(row)
|
const proNum = toScaleProNum(row)
|
||||||
proSet.add(proNum)
|
proSet.add(proNum)
|
||||||
const area = toFiniteNumber(row.landArea)
|
const area = toFiniteNumber(row.landArea)
|
||||||
const basicFee = toFiniteNumber(row.budgetFee)
|
const feeResolved = resolveScaleMethodFee(row, 'area')
|
||||||
|
const basicFee = feeResolved.basicFee
|
||||||
if (basicFee != null) hasTotalValue = true
|
if (basicFee != null) hasTotalValue = true
|
||||||
const basicFeeBasic = toFiniteNumber(row.budgetFeeBasic)
|
const basicFeeBasic = feeResolved.basicFeeBasic
|
||||||
const basicFeeOptional = toFiniteNumber(row.budgetFeeOptional)
|
const basicFeeOptional = feeResolved.basicFeeOptional
|
||||||
const remark = typeof row.remark === 'string' ? row.remark : ''
|
const remark = typeof row.remark === 'string' ? row.remark : ''
|
||||||
const hasValue =
|
const hasValue =
|
||||||
area != null ||
|
area != null ||
|
||||||
@ -1279,16 +1317,16 @@ const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod2 | n
|
|||||||
proNum,
|
proNum,
|
||||||
major,
|
major,
|
||||||
area: area ?? 0,
|
area: area ?? 0,
|
||||||
basicFee: basicFee ?? 0,
|
basicFee: toMoney(basicFee),
|
||||||
basicFormula: typeof row.basicFormula === 'string' ? row.basicFormula : '',
|
basicFormula: feeResolved.basicFormula,
|
||||||
basicFee_basic: basicFeeBasic ?? 0,
|
basicFee_basic: toMoney(basicFeeBasic),
|
||||||
optionalFormula: typeof row.optionalFormula === 'string' ? row.optionalFormula : '',
|
optionalFormula: feeResolved.optionalFormula,
|
||||||
basicFee_optional: basicFeeOptional ?? 0,
|
basicFee_optional: toMoney(basicFeeOptional),
|
||||||
serviceCoe: toFiniteNumberOrZero(row.consultCategoryFactor),
|
serviceCoe: toFiniteNumberOrZero(row.consultCategoryFactor),
|
||||||
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
||||||
processCoe: toFiniteNumber(row.workStageFactor) ?? 1,
|
processCoe: toFiniteNumber(row.workStageFactor) ?? 1,
|
||||||
proportion: toFiniteNumber(row.workRatio) ?? 1,
|
proportion: toFiniteNumber(row.workRatio) ?? 1,
|
||||||
fee: basicFee ?? 0,
|
fee: toMoney(basicFee),
|
||||||
remark
|
remark
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1298,10 +1336,10 @@ const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod2 | n
|
|||||||
return {
|
return {
|
||||||
proAmount: proSet.size > 0 ? proSet.size : 1,
|
proAmount: proSet.size > 0 ? proSet.size : 1,
|
||||||
area: sumNumbers(det.map(item => item.area)),
|
area: sumNumbers(det.map(item => item.area)),
|
||||||
basicFee: sumNumbers(det.map(item => item.basicFee)),
|
basicFee: toMoney(sumNumbers(det.map(item => item.basicFee))),
|
||||||
basicFee_basic: sumNumbers(det.map(item => item.basicFee_basic)),
|
basicFee_basic: toMoney(sumNumbers(det.map(item => item.basicFee_basic))),
|
||||||
basicFee_optional: sumNumbers(det.map(item => item.basicFee_optional)),
|
basicFee_optional: toMoney(sumNumbers(det.map(item => item.basicFee_optional))),
|
||||||
fee: sumNumbers(det.map(item => item.fee)),
|
fee: toMoney(sumNumbers(det.map(item => item.fee))),
|
||||||
det
|
det
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1480,27 +1518,12 @@ const loadHtFeeMethodsByRow = async (mainStorageKey: string, rowId: string) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildServiceGroupLabelToIdMap = (serviceIds: string[]): Map<string, number> => {
|
|
||||||
const map = new Map<string, number>()
|
|
||||||
for (const serviceId of serviceIds) {
|
|
||||||
const item = (serviceList as Record<string, any>)[serviceId]
|
|
||||||
if (!item) continue
|
|
||||||
const id = toSafeInteger(serviceId)
|
|
||||||
if (id == null) continue
|
|
||||||
const label = `${String(item.code || '').trim()} ${String(item.name || '').trim()}`.trim()
|
|
||||||
if (!label) continue
|
|
||||||
map.set(label, id)
|
|
||||||
}
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
const buildServiceTasks = async (
|
const buildServiceTasks = async (
|
||||||
contractId: string,
|
contractId: string,
|
||||||
serviceId: string,
|
serviceId: string
|
||||||
serviceLabelToId: Map<string, number>
|
|
||||||
): Promise<ExportTaskGroup[]> => {
|
): Promise<ExportTaskGroup[]> => {
|
||||||
const taskState = await zxFwPricingStore.loadKeyState<WorkContentStateLike>(`work-content-${contractId}-${serviceId}`)
|
const taskState = await zxFwPricingStore.loadKeyState<WorkContentStateLike>(`work-content-${contractId}-${serviceId}`)
|
||||||
return groupWorkContentTasks(taskState?.detailRows, { serviceLabelToId })
|
return groupWorkContentTasks(taskState?.detailRows)
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildAdditionalRowTasks = async (contractId: string, rowId: string): Promise<ExportTaskGroup[]> => {
|
const buildAdditionalRowTasks = async (contractId: string, rowId: string): Promise<ExportTaskGroup[]> => {
|
||||||
@ -1631,7 +1654,7 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
kvStore.getItem<ZxFwStorageLike>(`zxFW-${contractId}`),
|
kvStore.getItem<ZxFwStorageLike>(`zxFW-${contractId}`),
|
||||||
kvStore.getItem<DetailRowsStorageLike<FactorRowLike>>(`ht-consult-category-factor-v1-${contractId}`),
|
kvStore.getItem<DetailRowsStorageLike<FactorRowLike>>(`ht-consult-category-factor-v1-${contractId}`),
|
||||||
kvStore.getItem<DetailRowsStorageLike<FactorRowLike>>(`ht-major-factor-v1-${contractId}`),
|
kvStore.getItem<DetailRowsStorageLike<FactorRowLike>>(`ht-major-factor-v1-${contractId}`),
|
||||||
kvStore.getItem<HtBaseInfoLike>(`ht-base-info-${contractId}`)
|
zxFwPricingStore.loadKeyState<HtBaseInfoLike>(`ht-base-info-${contractId}`)
|
||||||
])
|
])
|
||||||
|
|
||||||
const contractState = zxFwPricingStore.getContractState(contractId)
|
const contractState = zxFwPricingStore.getContractState(contractId)
|
||||||
@ -1673,8 +1696,6 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
const serviceIdTexts = sortServiceIdsByDict(
|
const serviceIdTexts = sortServiceIdsByDict(
|
||||||
(selectedIds.length > 0 ? selectedIds : fallbackServiceIds).filter(hasServiceId)
|
(selectedIds.length > 0 ? selectedIds : fallbackServiceIds).filter(hasServiceId)
|
||||||
)
|
)
|
||||||
const serviceLabelToId = buildServiceGroupLabelToIdMap(serviceIdTexts)
|
|
||||||
|
|
||||||
const services = (
|
const services = (
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
serviceIdTexts.map(async serviceIdText => {
|
serviceIdTexts.map(async serviceIdText => {
|
||||||
@ -1699,7 +1720,7 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
const method4 = buildMethod4(method4Raw?.detailRows)
|
const method4 = buildMethod4(method4Raw?.detailRows)
|
||||||
const fee = buildServiceFee(sourceRow, method1, method2, method3, method4)
|
const fee = buildServiceFee(sourceRow, method1, method2, method3, method4)
|
||||||
const finalFee = buildServiceFinalFee(sourceRow, method1, method2, method3, method4)
|
const finalFee = buildServiceFinalFee(sourceRow, method1, method2, method3, method4)
|
||||||
const tasks = await buildServiceTasks(contractId, serviceIdText, serviceLabelToId)
|
const tasks = await buildServiceTasks(contractId, serviceIdText)
|
||||||
const process = Number(sourceRow?.process) === 1 ? 1 : 0
|
const process = Number(sourceRow?.process) === 1 ? 1 : 0
|
||||||
const service: ExportService = {
|
const service: ExportService = {
|
||||||
id: serviceId,
|
id: serviceId,
|
||||||
@ -1717,6 +1738,8 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
)
|
)
|
||||||
).filter((item): item is ExportService => Boolean(item))
|
).filter((item): item is ExportService => Boolean(item))
|
||||||
|
|
||||||
|
const fixedFinalFee = toFiniteNumber(fixedRow?.finalFee)
|
||||||
|
const serviceFinalFeeSum = sumNumbers(services.map(item => item.finalFee))
|
||||||
const fixedSubtotal = toFiniteNumber(fixedRow?.subtotal)
|
const fixedSubtotal = toFiniteNumber(fixedRow?.subtotal)
|
||||||
const serviceFeeSum = sumNumbers(services.map(item => item.fee))
|
const serviceFeeSum = sumNumbers(services.map(item => item.fee))
|
||||||
const fixedMethodSum = sumNumbers([
|
const fixedMethodSum = sumNumbers([
|
||||||
@ -1725,7 +1748,9 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
toFiniteNumber(fixedRow?.workload),
|
toFiniteNumber(fixedRow?.workload),
|
||||||
toFiniteNumber(fixedRow?.hourly)
|
toFiniteNumber(fixedRow?.hourly)
|
||||||
])
|
])
|
||||||
const serviceFee = fixedSubtotal ?? (serviceFeeSum !== 0 ? serviceFeeSum : fixedMethodSum)
|
const serviceFee =
|
||||||
|
fixedFinalFee ??
|
||||||
|
(services.length > 0 ? serviceFinalFeeSum : (fixedSubtotal ?? (serviceFeeSum !== 0 ? serviceFeeSum : fixedMethodSum)))
|
||||||
const [addtional, reserve] = await Promise.all([
|
const [addtional, reserve] = await Promise.all([
|
||||||
buildAdditionalExport(contractId),
|
buildAdditionalExport(contractId),
|
||||||
buildReserveExport(contractId)
|
buildReserveExport(contractId)
|
||||||
@ -1741,7 +1766,6 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
|||||||
const contractServiceCoesRaw = buildProjectServiceCoes(htConsultCategoryFactorRaw?.detailRows)
|
const contractServiceCoesRaw = buildProjectServiceCoes(htConsultCategoryFactorRaw?.detailRows)
|
||||||
const contractMajorCoesRaw = buildProjectMajorCoes(htMajorFactorRaw?.detailRows)
|
const contractMajorCoesRaw = buildProjectMajorCoes(htMajorFactorRaw?.detailRows)
|
||||||
|
|
||||||
|
|
||||||
contracts.push({
|
contracts.push({
|
||||||
name: isNonEmptyString(contract.name) ? contract.name : `合同段-${index + 1}`,
|
name: isNonEmptyString(contract.name) ? contract.name : `合同段-${index + 1}`,
|
||||||
serviceFee,
|
serviceFee,
|
||||||
@ -2291,9 +2315,19 @@ watch(
|
|||||||
class="pointer-events-auto rounded-xl border border-border bg-card px-4 py-3 text-foreground shadow-lg"
|
class="pointer-events-auto rounded-xl border border-border bg-card px-4 py-3 text-foreground shadow-lg"
|
||||||
@update:open="(val) => { if (!val) dismissReportToast() }"
|
@update:open="(val) => { if (!val) dismissReportToast() }"
|
||||||
>
|
>
|
||||||
|
<div class="flex items-start justify-between gap-2">
|
||||||
<ToastTitle class="text-sm font-semibold text-foreground">
|
<ToastTitle class="text-sm font-semibold text-foreground">
|
||||||
{{ reportExportStatus === 'running' ? '导出报表' : (reportExportStatus === 'success' ? '导出成功' : '导出失败') }}
|
{{ reportExportStatus === 'running' ? '导出报表' : (reportExportStatus === 'success' ? '导出成功' : '导出失败') }}
|
||||||
</ToastTitle>
|
</ToastTitle>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
class="h-6 w-6 p-0 text-muted-foreground hover:text-foreground"
|
||||||
|
@click="dismissReportToast"
|
||||||
|
>
|
||||||
|
<X class="h-3.5 w-3.5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
<ToastDescription class="mt-1 text-xs text-muted-foreground">{{ reportExportText }}</ToastDescription>
|
<ToastDescription class="mt-1 text-xs text-muted-foreground">{{ reportExportText }}</ToastDescription>
|
||||||
<!-- <div v-if="reportExportStatus === 'success' && reportExportBlobUrl" class="mt-2 flex items-center gap-2">
|
<!-- <div v-if="reportExportStatus === 'success' && reportExportBlobUrl" class="mt-2 flex items-center gap-2">
|
||||||
<Button size="sm" class="h-7 rounded-md px-3 text-xs" @click="openExportedReport">
|
<Button size="sm" class="h-7 rounded-md px-3 text-xs" @click="openExportedReport">
|
||||||
|
|||||||
@ -30,6 +30,7 @@ const props = withDefaults(
|
|||||||
categories: TypeLineCategory[]
|
categories: TypeLineCategory[]
|
||||||
storageKey?: string
|
storageKey?: string
|
||||||
defaultCategory?: string
|
defaultCategory?: string
|
||||||
|
persistActiveCategory?: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
scene: 'default',
|
scene: 'default',
|
||||||
@ -38,15 +39,28 @@ const props = withDefaults(
|
|||||||
metaText: '',
|
metaText: '',
|
||||||
copyText: '',
|
copyText: '',
|
||||||
storageKey: '',
|
storageKey: '',
|
||||||
defaultCategory: ''
|
defaultCategory: '',
|
||||||
|
persistActiveCategory: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const cacheKey = computed(() => props.storageKey || `type-line-active-cat-${props.scene}`)
|
const cacheKey = computed(() => props.storageKey || `type-line-active-cat-${props.scene}`)
|
||||||
|
|
||||||
|
const readStoredCategory = (key: string) => {
|
||||||
|
const sessionValue = sessionStorage.getItem(key)
|
||||||
|
if (sessionValue) return sessionValue
|
||||||
|
return localStorage.getItem(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeStoredCategory = (key: string, value: string) => {
|
||||||
|
sessionStorage.setItem(key, value)
|
||||||
|
localStorage.setItem(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
const resolveInitialCategory = () => {
|
const resolveInitialCategory = () => {
|
||||||
const defaultKey = props.defaultCategory || props.categories[0]?.key || ''
|
const defaultKey = props.defaultCategory || props.categories[0]?.key || ''
|
||||||
const savedKey = sessionStorage.getItem(cacheKey.value)
|
if (!props.persistActiveCategory) return defaultKey
|
||||||
|
const savedKey = readStoredCategory(cacheKey.value)
|
||||||
const validSavedKey = props.categories.some(item => item.key === savedKey)
|
const validSavedKey = props.categories.some(item => item.key === savedKey)
|
||||||
return validSavedKey ? (savedKey as string) : defaultKey
|
return validSavedKey ? (savedKey as string) : defaultKey
|
||||||
}
|
}
|
||||||
@ -56,6 +70,8 @@ const activeCategory = ref(resolveInitialCategory())
|
|||||||
watch(
|
watch(
|
||||||
() => [props.categories, props.defaultCategory, cacheKey.value],
|
() => [props.categories, props.defaultCategory, cacheKey.value],
|
||||||
() => {
|
() => {
|
||||||
|
const isCurrentValid = props.categories.some(item => item.key === activeCategory.value)
|
||||||
|
if (isCurrentValid) return
|
||||||
activeCategory.value = resolveInitialCategory()
|
activeCategory.value = resolveInitialCategory()
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
@ -65,7 +81,8 @@ watch(
|
|||||||
|
|
||||||
const switchCategory = (cat: string) => {
|
const switchCategory = (cat: string) => {
|
||||||
activeCategory.value = cat
|
activeCategory.value = cat
|
||||||
sessionStorage.setItem(cacheKey.value, cat)
|
if (!props.persistActiveCategory) return
|
||||||
|
writeStoredCategory(cacheKey.value, cat)
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeComponent = computed(() => {
|
const activeComponent = computed(() => {
|
||||||
|
|||||||
@ -189,6 +189,20 @@ const toRowMap = <TRow extends { id: string }>(rows?: TRow[]) => {
|
|||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parseScopedMajorId = (value: unknown) => {
|
||||||
|
const raw = String(value || '').trim()
|
||||||
|
const scoped = /^\d+::(.+)$/.exec(raw)
|
||||||
|
return (scoped ? String(scoped[1] || '').trim() : raw) || raw
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasScopedScaleRows = (rows?: Array<Record<string, unknown>>) =>
|
||||||
|
(rows || []).some(row => {
|
||||||
|
const id = String(row?.id || '')
|
||||||
|
if (/^\d+::/.test(id)) return true
|
||||||
|
const projectIndex = Number((row as { projectIndex?: unknown })?.projectIndex)
|
||||||
|
return Number.isFinite(projectIndex) && projectIndex > 1
|
||||||
|
})
|
||||||
|
|
||||||
const getDefaultConsultCategoryFactor = (serviceId: string | number) => {
|
const getDefaultConsultCategoryFactor = (serviceId: string | number) => {
|
||||||
const service = (getServiceDictById() as Record<string, ServiceLite | undefined>)[String(serviceId)]
|
const service = (getServiceDictById() as Record<string, ServiceLite | undefined>)[String(serviceId)]
|
||||||
return toFiniteNumberOrNull(service?.defCoe)
|
return toFiniteNumberOrNull(service?.defCoe)
|
||||||
@ -614,6 +628,44 @@ const resolveScaleRows = (
|
|||||||
return buildDefaultScaleRows(serviceId, consultCategoryFactorMap, majorFactorMap)
|
return buildDefaultScaleRows(serviceId, consultCategoryFactorMap, majorFactorMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizeScopedScaleRows = (
|
||||||
|
serviceId: string,
|
||||||
|
rowsFromDb: Array<Record<string, unknown>> | undefined,
|
||||||
|
consultCategoryFactorMap?: Map<string, number | null>,
|
||||||
|
majorFactorMap?: Map<string, number | null>
|
||||||
|
): ScaleRow[] => {
|
||||||
|
const rows = stripGroupScaleRows(rowsFromDb) as Array<Record<string, unknown>>
|
||||||
|
if (rows.length === 0) return []
|
||||||
|
const defaultConsultCategoryFactor =
|
||||||
|
consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
|
||||||
|
|
||||||
|
return rows.map(row => {
|
||||||
|
const parsedMajorId = parseScopedMajorId(row.id)
|
||||||
|
const resolvedMajorId = majorById.has(parsedMajorId) ? parsedMajorId : (majorIdAliasMap.get(parsedMajorId) || parsedMajorId)
|
||||||
|
const hasConsultCategoryFactor = hasOwn(row, 'consultCategoryFactor')
|
||||||
|
const hasMajorFactor = hasOwn(row, 'majorFactor')
|
||||||
|
const hasWorkStageFactor = hasOwn(row, 'workStageFactor')
|
||||||
|
const hasWorkRatio = hasOwn(row, 'workRatio')
|
||||||
|
return {
|
||||||
|
id: resolvedMajorId,
|
||||||
|
amount: toFiniteNumberOrNull(row.amount),
|
||||||
|
landArea: toFiniteNumberOrNull(row.landArea),
|
||||||
|
consultCategoryFactor:
|
||||||
|
toFiniteNumberOrNull(row.consultCategoryFactor) ??
|
||||||
|
(hasConsultCategoryFactor ? null : defaultConsultCategoryFactor),
|
||||||
|
majorFactor:
|
||||||
|
toFiniteNumberOrNull(row.majorFactor) ??
|
||||||
|
(hasMajorFactor ? null : (majorFactorMap?.get(resolvedMajorId) ?? getDefaultMajorFactorById(resolvedMajorId))),
|
||||||
|
workStageFactor:
|
||||||
|
toFiniteNumberOrNull(row.workStageFactor) ??
|
||||||
|
(hasWorkStageFactor ? null : 1),
|
||||||
|
workRatio:
|
||||||
|
toFiniteNumberOrNull(row.workRatio) ??
|
||||||
|
(hasWorkRatio ? null : 100)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 统一生成某合同下某个咨询服务四种计费方式的存储键。
|
// 统一生成某合同下某个咨询服务四种计费方式的存储键。
|
||||||
// 优先复用 Pinia store 当前约定的 key,避免与旧版 fallback key 脱节。
|
// 优先复用 Pinia store 当前约定的 key,避免与旧版 fallback key 脱节。
|
||||||
export const getPricingMethodDetailDbKeys = (
|
export const getPricingMethodDetailDbKeys = (
|
||||||
@ -778,6 +830,14 @@ export const getPricingMethodTotalsForService = async (params: {
|
|||||||
|
|
||||||
// 优先使用对应计费页的数据;不存在时回退合同段规模信息,再回退默认字典行。
|
// 优先使用对应计费页的数据;不存在时回退合同段规模信息,再回退默认字典行。
|
||||||
const excludeInvestmentCostAndAreaRows = params.options?.excludeInvestmentCostAndAreaRows === true
|
const excludeInvestmentCostAndAreaRows = params.options?.excludeInvestmentCostAndAreaRows === true
|
||||||
|
const investScaleRowsSource = stripGroupScaleRows(investData?.detailRows as Array<Record<string, unknown>> | undefined)
|
||||||
|
const landScaleRowsSource = stripGroupScaleRows(landData?.detailRows as Array<Record<string, unknown>> | undefined)
|
||||||
|
const scopedInvestRows = hasScopedScaleRows(investScaleRowsSource)
|
||||||
|
? normalizeScopedScaleRows(serviceId, investScaleRowsSource, consultCategoryFactorMap, majorFactorMap)
|
||||||
|
: null
|
||||||
|
const scopedLandRows = hasScopedScaleRows(landScaleRowsSource)
|
||||||
|
? normalizeScopedScaleRows(serviceId, landScaleRowsSource, consultCategoryFactorMap, majorFactorMap)
|
||||||
|
: null
|
||||||
const investScale = onlyCostScale
|
const investScale = onlyCostScale
|
||||||
? getOnlyCostScaleBudgetFee(
|
? getOnlyCostScaleBudgetFee(
|
||||||
serviceId,
|
serviceId,
|
||||||
@ -788,7 +848,7 @@ export const getPricingMethodTotalsForService = async (params: {
|
|||||||
industryId
|
industryId
|
||||||
)
|
)
|
||||||
: (() => {
|
: (() => {
|
||||||
const investRows = resolveScaleRows(
|
const investRows = scopedInvestRows || resolveScaleRows(
|
||||||
serviceId,
|
serviceId,
|
||||||
investData,
|
investData,
|
||||||
htData,
|
htData,
|
||||||
@ -802,7 +862,7 @@ export const getPricingMethodTotalsForService = async (params: {
|
|||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
|
|
||||||
const landRows = resolveScaleRows(
|
const landRows = scopedLandRows || resolveScaleRows(
|
||||||
serviceId,
|
serviceId,
|
||||||
landData,
|
landData,
|
||||||
htData,
|
htData,
|
||||||
|
|||||||
@ -857,7 +857,7 @@ export async function exportFile(fileName: string, data: any | (() => Promise<an
|
|||||||
|
|
||||||
// 按模板生成最终工作簿:填充封面、目录、各分表及汇总数据。
|
// 按模板生成最终工作簿:填充封面、目录、各分表及汇总数据。
|
||||||
async function generateTemplate(data) {
|
async function generateTemplate(data) {
|
||||||
// const downTextTmp = { richText: [{ font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: '常规' }, { font: { charset: 134, color: { theme: 1 }, italic: true, name: 'Calibri', size: 10, vertAlign: 'subscript' }, text: '下标' }] };
|
console.log(data)
|
||||||
// 编制说明 → 工作内容的前后默认项
|
// 编制说明 → 工作内容的前后默认项
|
||||||
let prefixIDs = [6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27];
|
let prefixIDs = [6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27];
|
||||||
let suffixIDs = [6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27];
|
let suffixIDs = [6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user