正在修复一些问题
This commit is contained in:
1 parent
ad363041c3
commit
66069ef0f1
11 files changed
+207
-142
No files matched your search
+46
-29
@@ -1,8 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineComponent, h, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { computed, defineComponent, h, nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
|
||||
import type { ComponentPublicInstance, PropType } from 'vue'
|
||||
import { AgGridVue } from 'ag-grid-vue3'
|
||||
import type { ColDef, GridOptions, ICellRendererParams } from 'ag-grid-community'
|
||||
import type { ColDef, GridApi, GridOptions, GridReadyEvent, ICellRendererParams } from 'ag-grid-community'
|
||||
import { myTheme, gridOptions, agGridWrapClass, agGridStyle } from '@/lib/diyAgGridOptions'
|
||||
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
|
||||
import { addNumbers, roundTo } from '@/lib/decimal'
|
||||
@@ -191,6 +191,11 @@ const setCurrentContractState = async (nextState: ZxFwViewState) => {
|
||||
await zxFwPricingStore.setContractState(props.contractId, nextState)
|
||||
}
|
||||
|
||||
const gridApi = shallowRef<GridApi<DetailRow> | null>(null)
|
||||
const onGridReady = (event: GridReadyEvent<DetailRow>) => {
|
||||
gridApi.value = event.api
|
||||
}
|
||||
|
||||
const pickerOpen = ref(false)
|
||||
const pickerTempIds = ref<string[]>([])
|
||||
const pickerSearch = ref('')
|
||||
@@ -594,11 +599,12 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
{
|
||||
headerName: '名称',
|
||||
field: 'name',
|
||||
minWidth: 180,
|
||||
minWidth: 150,
|
||||
flex: 3,
|
||||
wrapText: true,
|
||||
autoHeight: true,
|
||||
cellStyle: { lineHeight: '1.4', paddingTop: '4px', paddingBottom: '4px' },
|
||||
cellStyle:{ 'line-height': 1.6
|
||||
},
|
||||
valueGetter: params => {
|
||||
if (!params.data) return ''
|
||||
if (isFixedRow(params.data)) return ''
|
||||
@@ -609,8 +615,8 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
headerName: '工作环节',
|
||||
field: 'process',
|
||||
headerClass: 'ag-center-header zxfw-process-header',
|
||||
minWidth: 80,
|
||||
maxWidth: 100,
|
||||
minWidth: 150,
|
||||
maxWidth: 200,
|
||||
flex: 1,
|
||||
editable: false,
|
||||
sortable: false,
|
||||
@@ -707,15 +713,9 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
editable: false,
|
||||
valueGetter: params => {
|
||||
if (!params.data) return null
|
||||
if (isFixedRow(params.data)) return getFixedRowSubtotal()
|
||||
return sumNullableNumbers([
|
||||
params.data.investScale,
|
||||
params.data.landScale,
|
||||
params.data.workload,
|
||||
params.data.hourly
|
||||
])
|
||||
return params.data.subtotal
|
||||
},
|
||||
valueFormatter: params => (params.value == null ? '' : formatThousandsFlexible(params.value, 3))
|
||||
valueFormatter: params => (params.value == null ? '' : formatThousands(params.value, 2))
|
||||
},
|
||||
{
|
||||
headerName: '确认金额',
|
||||
@@ -727,18 +727,15 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
editable: params => !isFixedRow(params.data),
|
||||
valueGetter: params => {
|
||||
if (!params.data) return null
|
||||
if (isFixedRow(params.data)) {
|
||||
return sumNullableNumbers(
|
||||
detailRows.value.filter(r => !isFixedRow(r)).map(r => r.finalFee)
|
||||
)
|
||||
}
|
||||
if (params.data.finalFee != null) return params.data.finalFee
|
||||
return sumNullableNumbers([
|
||||
params.data.investScale,
|
||||
params.data.landScale,
|
||||
params.data.workload,
|
||||
params.data.hourly
|
||||
])
|
||||
|
||||
return params.data.finalFee
|
||||
},
|
||||
valueSetter: params => {
|
||||
const parsed = parseNumberOrNull(params.newValue, { precision: 2 })
|
||||
const val = parsed != null ? roundTo(parsed, 2) : null
|
||||
if (params.data.finalFee === val) return false
|
||||
params.data.finalFee = val
|
||||
return true
|
||||
},
|
||||
valueParser: params => {
|
||||
const parsed = parseNumberOrNull(params.newValue, { precision: 2 })
|
||||
@@ -749,7 +746,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
{
|
||||
headerName: '操作',
|
||||
field: 'actions',
|
||||
minWidth: 180,
|
||||
minWidth: 200,
|
||||
flex: 1.5,
|
||||
maxWidth: 220,
|
||||
editable: false,
|
||||
@@ -885,13 +882,17 @@ const fillPricingTotalsForServiceIds = async (serviceIds: string[]) => {
|
||||
const totals = totalsRaw ? sanitizePricingTotalsByService(String(row.id), totalsRaw) : null
|
||||
if (!totals) return row
|
||||
const newSubtotal = sumNullableNumbers([totals.investScale, totals.landScale, totals.workload, totals.hourly])
|
||||
// 判断用户是否手动编辑过 finalFee:旧值与旧四种方法之和不一致则视为手动编辑
|
||||
const oldSubtotal = sumNullableNumbers([row.investScale, row.landScale, row.workload, row.hourly])
|
||||
const userEdited = row.finalFee != null && oldSubtotal != null
|
||||
&& roundTo(row.finalFee, 2) !== roundTo(oldSubtotal, 2)
|
||||
return {
|
||||
...row,
|
||||
investScale: totals.investScale,
|
||||
landScale: totals.landScale,
|
||||
workload: totals.workload,
|
||||
hourly: totals.hourly,
|
||||
finalFee: newSubtotal != null ? roundTo(newSubtotal, 2) : null
|
||||
finalFee: userEdited ? row.finalFee : (newSubtotal != null ? roundTo(newSubtotal, 2) : null)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1110,6 +1111,11 @@ const initializeContractState = async () => {
|
||||
|| (data?.selectedCodes || []).map(code => serviceIdByCode.value.get(code)).filter((id): id is string => Boolean(id))
|
||||
await applySelection(idsFromStorage || [])
|
||||
await ensurePricingDetailRowsForCurrentSelection()
|
||||
// 重新获取所有已选服务的计价总额,确保从编辑页返回后 finalFee 和小计行都更新
|
||||
const allServiceIds = getSelectedServiceIdsWithoutFixed()
|
||||
if (allServiceIds.length > 0) {
|
||||
await fillPricingTotalsForServiceIds(allServiceIds)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('initializeContractState failed:', error)
|
||||
await setCurrentContractState({
|
||||
@@ -1159,10 +1165,20 @@ const handleCellValueChanged = async (event: any) => {
|
||||
const nextRows = currentState.detailRows.map(item =>
|
||||
item.id === row.id ? { ...item, finalFee: newValue } : item
|
||||
)
|
||||
const finalRows = applyFixedRowTotals(nextRows)
|
||||
await setCurrentContractState({
|
||||
...currentState,
|
||||
detailRows: applyFixedRowTotals(nextRows)
|
||||
detailRows: finalRows
|
||||
})
|
||||
// 真实更新小计行的 rowNode.data,确保 AG Grid 显示最新值
|
||||
const api = gridApi.value
|
||||
if (api) {
|
||||
const fixedRowData = finalRows.find(r => isFixedRow(r))
|
||||
const fixedNode = api.getRowNode(fixedBudgetRow.id)
|
||||
if (fixedNode && fixedRowData) {
|
||||
fixedNode.setData(fixedRowData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
@@ -1199,6 +1215,7 @@ onBeforeUnmount(() => {
|
||||
<div :class="agGridWrapClass">
|
||||
<AgGridVue :style="agGridStyle" :rowData="detailRows" :columnDefs="columnDefs"
|
||||
:gridOptions="detailGridOptions" :theme="myTheme" @cell-value-changed="handleCellValueChanged"
|
||||
@grid-ready="onGridReady"
|
||||
:enableClipboard="true" :localeText="AG_GRID_LOCALE_CN" :tooltipShowDelay="500" :headerHeight="30"
|
||||
:undoRedoCellEditing="true" :undoRedoCellEditingLimit="20" />
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { myTheme, agGridStyle } from '@/lib/diyAgGridOptions'
|
||||
import { workList } from '@/sql'
|
||||
import type { WorkType } from '@/sql'
|
||||
import { WorkType,TYPE_LABEL_MAP } from '@/sql'
|
||||
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||
import { Trash2 } from 'lucide-vue-next'
|
||||
|
||||
@@ -60,13 +60,7 @@ const zxFwPricingStore = useZxFwPricingStore()
|
||||
const gridApi = ref<GridApi<WorkContentRow> | null>(null)
|
||||
const rowData = ref<WorkContentRow[]>([])
|
||||
|
||||
const TYPE_LABEL_MAP: Record<number, WorkType> = {
|
||||
0: '基本工作',
|
||||
1: '可选工作',
|
||||
2: '日常顾问',
|
||||
3: '专项顾问',
|
||||
4: '附加工作'
|
||||
}
|
||||
|
||||
|
||||
const buildDefaultRowsFromDict = (): WorkContentRow[] => {
|
||||
const rows: WorkContentRow[] = []
|
||||
@@ -160,6 +154,11 @@ const contentCellRenderer = (params: ICellRendererParams<WorkContentRow>) => {
|
||||
const data = params.data
|
||||
if (!data) return ''
|
||||
const wrapper = document.createElement('div')
|
||||
wrapper.style.display = 'flex'
|
||||
wrapper.style.alignItems = 'center'
|
||||
wrapper.style.justifyContent = 'space-between'
|
||||
wrapper.style.gap = '6px'
|
||||
wrapper.style.width = '100%'
|
||||
wrapper.className = 'work-content-cell'
|
||||
// 自定义行不显示 checkbox,直接显示文本;空时显示 placeholder
|
||||
if (data.custom) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:subtitle="`合同ID:${contractIdText}`"
|
||||
:copy-text="contractIdText"
|
||||
:storage-key="activeTypeStorageKey"
|
||||
default-category="work-content"
|
||||
default-category="rate-fee"
|
||||
:categories="categories"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -157,6 +157,11 @@ const pricingCategories = computed<PricingCategoryItem[]>(() => [
|
||||
])
|
||||
|
||||
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>
|
||||
+5
-6
@@ -1637,9 +1637,8 @@ const exportReport = async () => {
|
||||
finishReportExportProgress(true, '报表导出完成', blobUrl)
|
||||
} catch (error) {
|
||||
console.error('export report failed:', error)
|
||||
if (reportExportToastOpen.value) {
|
||||
finishReportExportProgress(false, '报表导出失败,请重试')
|
||||
}
|
||||
finishReportExportProgress(false, '报表导出失败,请重试')
|
||||
|
||||
} finally {
|
||||
dataMenuOpen.value = false
|
||||
}
|
||||
@@ -2038,15 +2037,15 @@ watch(
|
||||
{{ reportExportStatus === 'running' ? '导出报表' : (reportExportStatus === 'success' ? '导出成功' : '导出失败') }}
|
||||
</ToastTitle>
|
||||
<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>
|
||||
<Button variant="ghost" size="sm" class="h-7 rounded-md px-2 text-xs text-muted-foreground" @click="dismissReportToast">
|
||||
关闭
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else class="mt-2 flex items-center gap-2">
|
||||
</div> -->
|
||||
<div class="mt-2 flex items-center gap-2">
|
||||
<div class="h-1.5 flex-1 overflow-hidden rounded-full bg-muted">
|
||||
<div
|
||||
class="h-full transition-all duration-300"
|
||||
|
||||
+16
-10
@@ -17,6 +17,7 @@ export interface ZxFwDetailRow {
|
||||
workload: number | null
|
||||
hourly: number | null
|
||||
subtotal?: number | null
|
||||
finalFee?: number | null
|
||||
actions?: unknown
|
||||
}
|
||||
|
||||
@@ -95,6 +96,7 @@ const normalizeRows = (rows: unknown): ZxFwDetailRow[] =>
|
||||
workload: toFiniteNumberOrNull(row.workload),
|
||||
hourly: toFiniteNumberOrNull(row.hourly),
|
||||
subtotal: toFiniteNumberOrNull(row.subtotal),
|
||||
finalFee: toFiniteNumberOrNull(row.finalFee),
|
||||
actions: row.actions
|
||||
}
|
||||
}).filter(row => row.id)
|
||||
@@ -107,7 +109,6 @@ const applyRowSubtotals = (rows: ZxFwDetailRow[]): ZxFwDetailRow[] => {
|
||||
const totalWorkload = sumNullableNumbers(nonFixedRows.map(row => row.workload))
|
||||
const totalHourly = sumNullableNumbers(nonFixedRows.map(row => row.hourly))
|
||||
const fixedSubtotal = sumNullableNumbers([totalInvestScale, totalLandScale, totalWorkload, totalHourly])
|
||||
|
||||
return normalized.map(row => {
|
||||
if (row.id === FIXED_ROW_ID) {
|
||||
return {
|
||||
@@ -116,7 +117,9 @@ const applyRowSubtotals = (rows: ZxFwDetailRow[]): ZxFwDetailRow[] => {
|
||||
landScale: round3Nullable(totalLandScale),
|
||||
workload: round3Nullable(totalWorkload),
|
||||
hourly: round3Nullable(totalHourly),
|
||||
subtotal: round3Nullable(fixedSubtotal)
|
||||
subtotal: round3Nullable(fixedSubtotal),
|
||||
finalFee: row.finalFee,
|
||||
|
||||
}
|
||||
}
|
||||
const subtotal = sumNullableNumbers([
|
||||
@@ -127,7 +130,9 @@ const applyRowSubtotals = (rows: ZxFwDetailRow[]): ZxFwDetailRow[] => {
|
||||
])
|
||||
return {
|
||||
...row,
|
||||
subtotal: round3Nullable(subtotal)
|
||||
subtotal: round3Nullable(subtotal),
|
||||
finalFee: round3Nullable(subtotal),
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -181,6 +186,7 @@ const isSameRows = (a: ZxFwDetailRow[] | undefined, b: ZxFwDetailRow[] | undefin
|
||||
if (!isSameNullableNumber(l.workload, r.workload)) return false
|
||||
if (!isSameNullableNumber(l.hourly, r.hourly)) return false
|
||||
if (!isSameNullableNumber(l.subtotal, r.subtotal)) return false
|
||||
if (!isSameNullableNumber(l.finalFee, r.finalFee)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -568,7 +574,7 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
||||
mainStorageKeyRaw: string | number,
|
||||
force = false
|
||||
): Promise<HtFeeMainState<TRow> | null> => {
|
||||
|
||||
|
||||
const mainStorageKey = toKey(mainStorageKeyRaw)
|
||||
if (!mainStorageKey) return null
|
||||
if (!force) {
|
||||
@@ -663,7 +669,7 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
||||
) => {
|
||||
const mainStorageKey = toKey(mainStorageKeyRaw)
|
||||
const rowId = toKey(rowIdRaw)
|
||||
|
||||
|
||||
if (!mainStorageKey || !rowId) return false
|
||||
const storageKey = getHtFeeMethodStorageKey(mainStorageKey, rowId, method)
|
||||
if (!storageKey) return false
|
||||
@@ -986,7 +992,10 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
||||
: null
|
||||
const current = contracts.value[contractId]
|
||||
if (raw) {
|
||||
console.log(raw,'init')
|
||||
|
||||
const normalized = normalizeState(raw)
|
||||
console.log(normalized)
|
||||
if (!current || !isSameState(current, normalized)) {
|
||||
contracts.value[contractId] = normalized
|
||||
touchVersion(contractId)
|
||||
@@ -1039,12 +1048,10 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
||||
field: ZxFwPricingField
|
||||
value: number | null | undefined
|
||||
}) => {
|
||||
|
||||
const contractId = toKey(params.contractId)
|
||||
if (!contractId) return false
|
||||
|
||||
if (!contracts.value[contractId]) {
|
||||
await loadContract(contractId)
|
||||
}
|
||||
|
||||
|
||||
const current = contracts.value[contractId]
|
||||
if (!current?.detailRows?.length) return false
|
||||
@@ -1090,7 +1097,6 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
||||
if (!contractId) return null
|
||||
const state = contracts.value[contractId]
|
||||
if (!state?.detailRows?.length) return null
|
||||
|
||||
const fixedRow = state.detailRows.find(row => String(row.id || '') === FIXED_ROW_ID)
|
||||
const fixedFinalFee = toFiniteNumberOrNull(fixedRow?.finalFee)
|
||||
if (fixedFinalFee != null) return round3(fixedFinalFee)
|
||||
|
||||
+59
-1
@@ -11,7 +11,16 @@ const toFiniteNumber = (value: unknown) => {
|
||||
const num = Number(value)
|
||||
return Number.isFinite(num) ? num : 0
|
||||
}
|
||||
export type WorkType = '基本工作' | '可选工作' | '日常顾问' | '专项顾问' | '附加工作' | '自定义'
|
||||
|
||||
export const TYPE_LABEL_MAP: Record<number, WorkType> = {
|
||||
0: '基本工作',
|
||||
1: '可选工作',
|
||||
2: '日常顾问',
|
||||
3: '专项顾问',
|
||||
4: '附加工作',
|
||||
5:'自定义'
|
||||
}
|
||||
export const industryTypeList = [
|
||||
{ id: '0', name: '公路工程', type: 'isRoad' },
|
||||
{ id: '1', name: '铁路工程', type: 'isRailway' },
|
||||
@@ -337,6 +346,56 @@ export const workList = {
|
||||
146: { text: '作为造价咨询服务总体协调单位,依据造价技术标准的具体条款或委托方的个性化需求,进一步细化各项工作的具体要求,检查其他服务单位的造价文件的组成完整性、电子文件格式是否符合要求、电子版与纸质版是否对应、造价文件报表的规范性', serviceid: -1, order: 147, type: 4 },
|
||||
147: { text: '作为造价咨询服务总体协调单位,负责总体协调其他咨询人或专家团队的工作,确保各方在项目服务中的沟通顺畅,监控造价咨询服务的进展情况,确保各咨询人按时完成工作', serviceid: -1, order: 148, type: 4 },
|
||||
}
|
||||
//工作内容树形关系表
|
||||
export const wholeProcessTasks = [
|
||||
{
|
||||
fid: 0,
|
||||
industry: 0,
|
||||
sid: [6, 7, 8, 9, 11, 13],
|
||||
},
|
||||
{
|
||||
fid: 0,
|
||||
industry: 1,
|
||||
sid: [6, 7, 8, 9, 10, 12, 13],
|
||||
},
|
||||
{
|
||||
fid: 0,
|
||||
industry: 2,
|
||||
sid: [6, 7, 8, 9, 11, 13],
|
||||
},
|
||||
{
|
||||
fid: 2,
|
||||
industry: 0,
|
||||
sid: [6, 7, 8],
|
||||
},
|
||||
{
|
||||
fid: 2,
|
||||
industry: 1,
|
||||
sid: [6, 7, 8],
|
||||
},
|
||||
{
|
||||
fid: 2,
|
||||
industry: 2,
|
||||
sid: [6, 7, 8],
|
||||
},
|
||||
{
|
||||
fid: 3,
|
||||
industry: 0,
|
||||
sid: [9, 11, 13],
|
||||
},
|
||||
{
|
||||
fid: 4,
|
||||
industry: 1,
|
||||
sid: [9, 10, 12, 13],
|
||||
},
|
||||
{
|
||||
fid: 3,
|
||||
industry: 2,
|
||||
sid: [9, 11, 13],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
||||
let costScaleCal = [
|
||||
{ code: 'C1-1', staLine: 0, endLine: 100, basic: { staPrice: 0, rate: 0.01 }, optional: { staPrice: 0, rate: 0.002 } },
|
||||
@@ -372,7 +431,6 @@ let areaScaleCal = [
|
||||
|
||||
|
||||
|
||||
export type WorkType = '基本工作' | '可选工作' | '日常顾问' | '专项顾问' | '附加工作' | '自定义'
|
||||
|
||||
export type IndustryType = (typeof industryTypeList)[number]['type']
|
||||
type DictItem = Record<string, any>
|
||||
|
||||
+2
-2
@@ -144,10 +144,10 @@ html {
|
||||
}
|
||||
|
||||
/* When one column uses auto-height rows, keep other columns vertically centered. */
|
||||
.xmMx .ag-row .ag-cell-wrapper {
|
||||
/* .xmMx .ag-row .ag-cell-wrapper {
|
||||
|
||||
height: 100%;
|
||||
}
|
||||
} */
|
||||
|
||||
.xmMx .ag-row .ag-cell:not(.ag-cell-auto-height) .ag-cell-wrapper.ag-row-group {
|
||||
align-items: center;
|
||||
|
||||
Reference in new issue
Block a user