170 lines
5.0 KiB
Vue
170 lines
5.0 KiB
Vue
<template>
|
||
<TypeLine
|
||
scene="zxfw-pricing-tab"
|
||
:title="`${contractName ? `合同段:${contractName} · ` : ''}${fwName}计算`"
|
||
:subtitle="`合同ID:${contractId}`"
|
||
:copy-text="contractId"
|
||
:storage-key="`zxfw-pricing-active-cat-${contractId}-${serviceId}`"
|
||
:default-category="defaultCategory"
|
||
:categories="pricingCategories"
|
||
/>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, defineAsyncComponent, defineComponent, h, markRaw, type Component } from 'vue'
|
||
import TypeLine from '@/layout/typeLine.vue'
|
||
import MethodUnavailableNotice from '@/features/shared/components/MethodUnavailableNotice.vue'
|
||
|
||
interface ServiceMethodType {
|
||
scale?: boolean | null
|
||
onlyCostScale?: boolean | null
|
||
amount?: boolean | null
|
||
workDay?: boolean | null
|
||
}
|
||
|
||
const props = defineProps<{
|
||
contractId: string
|
||
contractName?: string
|
||
serviceId: string|number
|
||
fwName:string
|
||
type?: ServiceMethodType
|
||
projectInfoKey?: string
|
||
}>()
|
||
|
||
interface PricingCategoryItem {
|
||
key: string
|
||
label: string
|
||
component: Component
|
||
}
|
||
|
||
const resolveMethodEnabled = (value: boolean | null | undefined, fallback = true) =>
|
||
typeof value === 'boolean' ? value : fallback
|
||
|
||
const methodAvailability = computed(() => {
|
||
const scale = resolveMethodEnabled(props.type?.scale, true)
|
||
const onlyCostScale = resolveMethodEnabled(props.type?.onlyCostScale, false)
|
||
const amount = resolveMethodEnabled(props.type?.amount, true)
|
||
const workDay = resolveMethodEnabled(props.type?.workDay, true)
|
||
return {
|
||
investmentScale: scale,
|
||
landScale: scale && !onlyCostScale,
|
||
workload: amount,
|
||
hourly: workDay
|
||
}
|
||
})
|
||
|
||
const createPricingPane = (name: string) =>
|
||
markRaw(
|
||
defineComponent({
|
||
name,
|
||
setup() {
|
||
const AsyncPricingView = defineAsyncComponent({
|
||
loader: () => import(`@/features/pricing/components/${name}.vue`),
|
||
onError: err => {
|
||
console.error('加载 PricingMethodView 组件失败:', err)
|
||
}
|
||
})
|
||
|
||
return () => h(AsyncPricingView, {
|
||
contractId: props.contractId,
|
||
serviceId: props.serviceId,
|
||
projectInfoKey: props.projectInfoKey
|
||
})
|
||
}
|
||
})
|
||
)
|
||
|
||
const createMethodUnavailablePane = (title: string, message: string) =>
|
||
markRaw(
|
||
defineComponent({
|
||
name: 'MethodUnavailablePane',
|
||
setup() {
|
||
return () => h(MethodUnavailableNotice, { title, message })
|
||
}
|
||
})
|
||
)
|
||
|
||
const investmentScaleView = createPricingPane('InvestmentScalePricingPane')
|
||
const landScaleView = createPricingPane('LandScalePricingPane')
|
||
const workloadView = createPricingPane('WorkloadPricingPane')
|
||
const hourlyView = createPricingPane('HourlyPricingPane')
|
||
|
||
const workContentPane = markRaw(
|
||
defineComponent({
|
||
name: 'WorkContentPane',
|
||
setup() {
|
||
const AsyncWorkContentGrid = defineAsyncComponent({
|
||
loader: () => import('@/features/shared/components/WorkContentGrid.vue'),
|
||
onError: err => {
|
||
console.error('加载 WorkContentGrid 组件失败:', err)
|
||
}
|
||
})
|
||
|
||
return () => h(AsyncWorkContentGrid, {
|
||
title: '工作内容',
|
||
storageKey: `work-content-${props.contractId}-${props.serviceId}`,
|
||
contractId: props.contractId,
|
||
projectInfoKey: props.projectInfoKey,
|
||
serviceId: props.serviceId,
|
||
dictMode: 'service'
|
||
})
|
||
}
|
||
})
|
||
)
|
||
|
||
const investmentScaleUnavailableView = createMethodUnavailablePane(
|
||
'该服务不适用投资规模法',
|
||
'当前服务未启用规模法,投资规模法不可编辑。'
|
||
)
|
||
const landScaleUnavailableView = createMethodUnavailablePane(
|
||
'该服务不适用用地规模法',
|
||
'当前服务仅支持投资规模法,用地规模法不可编辑。'
|
||
)
|
||
const workloadUnavailableView = createMethodUnavailablePane(
|
||
'该服务不适用工作量法',
|
||
'当前服务未启用工作量法,工作量法不可编辑。'
|
||
)
|
||
const hourlyUnavailableView = createMethodUnavailablePane(
|
||
'该服务不适用工时法',
|
||
'当前服务未启用工时法,工时法不可编辑。'
|
||
)
|
||
|
||
const pricingCategories = computed<PricingCategoryItem[]>(() => [
|
||
|
||
{
|
||
key: 'investment-scale-method',
|
||
label: '投资规模法',
|
||
component: methodAvailability.value.investmentScale ? investmentScaleView : investmentScaleUnavailableView
|
||
},
|
||
{
|
||
key: 'land-scale-method',
|
||
label: '用地规模法',
|
||
component: methodAvailability.value.landScale ? landScaleView : landScaleUnavailableView
|
||
},
|
||
{
|
||
key: 'workload-method',
|
||
label: '工作量法',
|
||
component: methodAvailability.value.workload ? workloadView : workloadUnavailableView
|
||
},
|
||
{
|
||
key: 'hourly-method',
|
||
label: '工时法',
|
||
component: methodAvailability.value.hourly ? hourlyView : hourlyUnavailableView
|
||
},
|
||
{
|
||
key: 'work-content',
|
||
label: '工作内容',
|
||
component: workContentPane
|
||
},
|
||
])
|
||
|
||
const defaultCategory = computed(() => {
|
||
const m = methodAvailability.value
|
||
if (m.investmentScale) return 'investment-scale-method'
|
||
if (m.landScale) return 'land-scale-method'
|
||
if (m.workload) return 'workload-method'
|
||
if (m.hourly) return 'hourly-method'
|
||
return 'work-content'
|
||
})
|
||
</script>
|