diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 4c27dbb..134968b 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -9,7 +9,9 @@
"Bash(rmdir views/pricingView common)",
"Bash(cd:*)",
"Bash(bun run:*)",
- "Bash(grep:*)"
+ "Bash(grep:*)",
+ "mcp__context7__resolve-library-id",
+ "mcp__context7__query-docs"
]
}
}
diff --git a/src/components/ht/HtAdditionalWorkFee.vue b/src/components/ht/HtAdditionalWorkFee.vue
index bf305fd..17b17af 100644
--- a/src/components/ht/HtAdditionalWorkFee.vue
+++ b/src/components/ht/HtAdditionalWorkFee.vue
@@ -10,7 +10,7 @@ const props = defineProps<{
const STORAGE_KEY = computed(() => `htExtraFee-${props.contractId}-additional-work`)
const additionalWorkNames = computed(() =>
- additionalWorkList.map(item => String(item?.name || '').trim()).filter(Boolean)
+ additionalWorkList.map(item => ({id:item?.id,name:item?.name}))
)
diff --git a/src/components/ht/HtReserveFee.vue b/src/components/ht/HtReserveFee.vue
index e78620f..5ca514a 100644
--- a/src/components/ht/HtReserveFee.vue
+++ b/src/components/ht/HtReserveFee.vue
@@ -10,7 +10,7 @@ const props = defineProps<{
const STORAGE_KEY = computed(() => `htExtraFee-${props.contractId}-reserve`)
const reserveFeeNames = computed(() =>
- reserveList.map(item => String(item?.name || '').trim()).filter(Boolean)
+ reserveList.map(item => ({name:item.name,id:item.id}))
)
diff --git a/src/components/ht/zxFw.vue b/src/components/ht/zxFw.vue
index 69a851a..34e32b3 100644
--- a/src/components/ht/zxFw.vue
+++ b/src/components/ht/zxFw.vue
@@ -3,7 +3,7 @@ import { computed, defineComponent, h, nextTick, onActivated, onBeforeUnmount, o
import type { ComponentPublicInstance, PropType } from 'vue'
import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef, GridOptions, ICellRendererParams } from 'ag-grid-community'
-import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
+import { myTheme, gridOptions, agGridWrapClass, agGridStyle } from '@/lib/diyAgGridOptions'
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
import { addNumbers } from '@/lib/decimal'
import { parseNumberOrNull } from '@/lib/number'
@@ -1150,8 +1150,8 @@ onBeforeUnmount(() => {
按服务词典生成
-
-
+
diff --git a/src/components/pricing/InvestmentScalePricingPane.vue b/src/components/pricing/InvestmentScalePricingPane.vue
index 953c67b..952bdcb 100644
--- a/src/components/pricing/InvestmentScalePricingPane.vue
+++ b/src/components/pricing/InvestmentScalePricingPane.vue
@@ -3,7 +3,7 @@ import { computed, onActivated, onBeforeUnmount, onMounted, ref } from 'vue'
import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef, ColGroupDef } from 'ag-grid-community'
import { getMajorDictEntries, getServiceDictItemById, industryTypeList, isMajorIdInIndustryScope } from '@/sql'
-import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
+import { myTheme, gridOptions, agGridWrapClass, agGridStyle } from '@/lib/diyAgGridOptions'
import { addNumbers, decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
import { formatThousandsFlexible } from '@/lib/numberFormat'
import { syncPricingTotalToZxFw } from '@/lib/zxFwPricingSync'
@@ -1365,8 +1365,8 @@ const processCellFromClipboard = (params: any) => {
-
-
-
-
+
-
+
()
const tabStore = useTabStore()
const zxFwPricingStore = useZxFwPricingStore()
-
const createRowId = () => `fee-method-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`
const createDefaultRow = (name = ''): FeeMethodRow => ({
id: createRowId(),
@@ -89,6 +88,7 @@ const createDefaultRow = (name = ''): FeeMethodRow => ({
quantityUnitPriceFee: null
})
const SUMMARY_ROW_ID = 'fee-method-summary'
+
const isSummaryRow = (row: FeeMethodRow | null | undefined) => row?.id === SUMMARY_ROW_ID
const toFinite = (value: number | null | undefined) =>
typeof value === 'number' && Number.isFinite(value) ? value : 0
@@ -240,7 +240,7 @@ const hydrateRowsFromMethodStores = async (rows: FeeMethodRow[]): Promise
Array.isArray(props.fixedNames)
- ? props.fixedNames.map(item => String(item || '').trim()).filter(Boolean)
+ ? props.fixedNames.map(item => ({name:item.name,id:item.id}))
: []
)
const hasFixedNames = computed(() => fixedNames.value.length > 0)
@@ -328,12 +328,12 @@ const toLegacyQuantityUnitPriceFee = (row: LegacyFeeRow) => {
}
const mergeWithStoredRows = (rowsFromDb: unknown): FeeMethodRow[] => {
+
const sourceRows = (Array.isArray(rowsFromDb) ? rowsFromDb : []).filter(
item => (item as Partial)?.id !== SUMMARY_ROW_ID
)
const rows = sourceRows.map(item => {
const row = item as Partial & LegacyFeeRow
-
return {
id: typeof row.id === 'string' && row.id ? row.id : createRowId(),
name:
@@ -349,13 +349,15 @@ const mergeWithStoredRows = (rowsFromDb: unknown): FeeMethodRow[] => {
: toLegacyQuantityUnitPriceFee(row)
} as FeeMethodRow
})
+
if (hasFixedNames.value) {
+
const byName = new Map(rows.map(row => [row.name, row]))
- return fixedNames.value.map((name, index) => {
- const fromDb = byName.get(name)
+ return fixedNames.value.map((item, index) => {
+ const fromDb = byName.get(item.name)
return {
- id: fromDb?.id || `fee-method-fixed-${index}`,
- name,
+ id: item?.id || `fee-method-fixed-${index}`,
+ name:item.name,
rateFee: fromDb?.rateFee ?? null,
hourlyFee: fromDb?.hourlyFee ?? null,
quantityUnitPriceFee: fromDb?.quantityUnitPriceFee ?? null
@@ -384,7 +386,6 @@ const saveToIndexedDB = async (force = false) => {
const loadFromIndexedDB = async () => {
try {
const data = await zxFwPricingStore.loadHtFeeMainState(props.storageKey)
-
const mergedRows = mergeWithStoredRows(data?.detailRows)
detailRows.value = await hydrateRowsFromMethodStores(mergedRows)
await saveToIndexedDB(true)
@@ -423,6 +424,7 @@ const clearRow = async (id: string) => {
const editRow = (id: string) => {
const row = detailRows.value.find(item => item.id === id)
if (!row) return
+ console.log(id)
tabStore.openTab({
id: `ht-fee-edit-${props.storageKey}-${id}`,
title: `费用编辑-${row.name || '未命名'}`,
@@ -694,9 +696,9 @@ onBeforeUnmount(() => {
-
+
{
const sid = Number(props.serviceId)
filtered = entries.filter(e => e.serviceid === sid)
} else if (props.dictMode === 'additional') {
- filtered = entries.filter(e => e.serviceid === -1)
+ filtered = entries.filter(e => e.serviceid === -1 && props.storageKey.split('-').at(-1) =='2')
} else {
return []
}
@@ -188,7 +188,13 @@ const columnDefs: ColDef[] = [
flex: 2,
editable: params => Boolean(params.data?.custom),
cellClass: params => (params.data?.custom ? 'editable-cell-line' : ''),
+ cellClassRules: {
+ 'editable-cell-empty': params => Boolean(params.data?.custom) && (params.value == null || params.value === '')
+ },
valueParser: params => String(params.newValue || '').trim(),
+ wrapText: true,
+ autoHeight: true,
+ cellStyle: { whiteSpace: 'normal', lineHeight: '1.5' },
cellRenderer: contentCellRenderer
},
{
@@ -267,7 +273,7 @@ onBeforeUnmount(() => {
{
display: flex;
width: 100%;
align-items: center;
- justify-content: space-between;
gap: 8px;
}
:deep(.work-content-text) {
min-width: 0;
flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
+ white-space: normal;
+ word-break: break-word;
+ line-height: 1.5;
}
:deep(.work-content-check) {
diff --git a/src/components/shared/XmFactorGrid.vue b/src/components/shared/XmFactorGrid.vue
index 7becc25..2a63ee7 100644
--- a/src/components/shared/XmFactorGrid.vue
+++ b/src/components/shared/XmFactorGrid.vue
@@ -2,7 +2,7 @@
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef, FirstDataRenderedEvent, GridApi, GridReadyEvent, GridSizeChangedEvent } from 'ag-grid-community'
-import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
+import { myTheme, gridOptions, agGridWrapClass, agGridStyle } from '@/lib/diyAgGridOptions'
import { parseNumberOrNull } from '@/lib/number'
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
import { useKvStore } from '@/pinia/kv'
@@ -325,9 +325,9 @@ onBeforeUnmount(() => {
-
-
-
+ ()
-
const sourceTitleText = computed(() => props.sourceTitle || '费用明细')
const rowNameText = computed(() => props.rowName || '未命名')
const contractIdText = computed(() => String(props.contractId || '').trim())
@@ -92,11 +91,9 @@ const hourlyFeePane = markRaw(
})
)
-const isAdditionalWork = computed(() => props.sourceTitle === '附加工作费')
const isReserveFee = computed(() => props.sourceTitle === '预备费')
const showWorkContent = computed(() => {
if (isReserveFee.value) return false
- if (isAdditionalWork.value) return props.rowName === '咨询服务协调工作'
return true
})
@@ -110,11 +107,10 @@ const workContentPane = markRaw(
console.error('加载 WorkContentGrid 组件失败:', err)
}
})
-
return () => h(AsyncWorkContentGrid, {
title: '工作内容',
storageKey: `work-content-${props.storageKey}-${props.rowId}`,
- dictMode: isAdditionalWork.value ? 'additional' : 'none'
+ dictMode: 'additional'
})
}
})
diff --git a/src/layout/tab.vue b/src/layout/tab.vue
index 4c1513f..d6859f5 100644
--- a/src/layout/tab.vue
+++ b/src/layout/tab.vue
@@ -23,7 +23,7 @@ import {
} from 'reka-ui'
import { decodeZwArchive, encodeZwArchive, ZW_FILE_EXTENSION } from '@/lib/zwArchive'
import { addNumbers, roundTo } from '@/lib/decimal'
-import { exportFile, serviceList, additionalWorkList } from '@/sql'
+import { exportFile, serviceList } from '@/sql'
interface DataEntry {
key: string
@@ -1290,12 +1290,7 @@ const loadHtFeeMethodsByRow = async (mainStorageKey: string, rowId: string) => {
}
}
-const buildAdditionalDetailCode = (index: number, name: string): unknown => {
- const dictionaryIndex = additionalWorkList.findIndex((item:any) => item === name)
- const useIndex = dictionaryIndex >= 0 ? dictionaryIndex : index
- const suffix = useIndex === 0 ? 'F' : useIndex === 1 ? 'X' : String(useIndex + 1)
- return createRichTextCode('C', suffix)
-}
+
const buildAdditionalExport = async (contractId: string): Promise => {
const storageKey = `htExtraFee-${contractId}-additional-work`
diff --git a/src/layout/typeLine.vue b/src/layout/typeLine.vue
index 65687b7..2a3948c 100644
--- a/src/layout/typeLine.vue
+++ b/src/layout/typeLine.vue
@@ -75,10 +75,7 @@ const activeComponent = computed(() => {
const copyBtnText = ref('复制')
const sheetOpen = ref(false)
-const titleRef = ref(null)
-const isTitleOverflow = ref(false)
-const subtitleRef = ref(null)
-const isSubtitleOverflow = ref(false)
+
let copyBtnTimer: ReturnType | null = null
let titleOverflowRafId: number | null = null
diff --git a/src/lib/diyAgGridOptions.ts b/src/lib/diyAgGridOptions.ts
index 6c65787..4eb0fe4 100644
--- a/src/lib/diyAgGridOptions.ts
+++ b/src/lib/diyAgGridOptions.ts
@@ -20,6 +20,12 @@ export const myTheme = themeQuartz.withParams({
dataBackgroundColor: '#fefefe'
})
+// AG Grid 容器通用 class(占满父容器,配合父元素为 flex/grid 且有明确高度使用)
+export const agGridWrapClass = 'ag-theme-quartz h-full min-h-0 w-full flex-1'
+
+// AG Grid 组件通用 style(撑满容器 div)
+export const agGridStyle = { height: '100%' }
+
export const gridOptions: GridOptions = {
treeData: true,
animateRows: true,
diff --git a/src/sql.ts b/src/sql.ts
index 8db6bbe..afb644b 100644
--- a/src/sql.ts
+++ b/src/sql.ts
@@ -152,7 +152,7 @@ export const expertList = {
};
export const additionalWorkList = [
- {
+ { id:'1',
code: {
richText: [
{ font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: 'C' },
@@ -161,7 +161,7 @@ export const additionalWorkList = [
},
name: '人员驻场服务及其他附加工作'
},
- {
+ {id:'2',
code: {
richText: [
{ font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: 'C' },
@@ -174,6 +174,7 @@ export const additionalWorkList = [
export const reserveList = [
{
+ id:1,
code: {
richText: [
{ font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: 'Y' },
diff --git a/src/style.css b/src/style.css
index 2d6f3c2..ecff113 100644
--- a/src/style.css
+++ b/src/style.css
@@ -295,6 +295,7 @@ html {
color: #94a3b8 !important;
font-style: italic;
opacity: 1 !important;
+ border-bottom: none !important;
}
.xmMx .ag-cell.editable-cell-empty,
diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo
index 49576d1..ce096ea 100644
--- a/tsconfig.tsbuildinfo
+++ b/tsconfig.tsbuildinfo
@@ -1 +1 @@
-{"root":["./src/main.ts","./src/sql.ts","./src/components/ui/button/index.ts","./src/components/ui/card/index.ts","./src/components/ui/scroll-area/index.ts","./src/components/ui/tooltip/index.ts","./src/lib/decimal.ts","./src/lib/diyaggridoptions.ts","./src/lib/number.ts","./src/lib/numberformat.ts","./src/lib/pricingmethodtotals.ts","./src/lib/pricingscalefee.ts","./src/lib/projectworkspace.ts","./src/lib/utils.ts","./src/lib/workspace.ts","./src/lib/xmfactordefaults.ts","./src/lib/zwarchive.ts","./src/lib/zxfwpricingsync.ts","./src/pinia/kv.ts","./src/pinia/tab.ts","./src/pinia/zxfwpricing.ts","./src/pinia/plugin/indexdb.ts","./src/pinia/plugin/types.d.ts","./src/app.vue","./src/components/common/hourlyfeegrid.vue","./src/components/common/htfeegrid.vue","./src/components/common/htfeemethodgrid.vue","./src/components/common/methodunavailablenotice.vue","./src/components/common/xmfactorgrid.vue","./src/components/common/xmcommonaggrid.vue","./src/components/ui/button/button.vue","./src/components/ui/card/card.vue","./src/components/ui/card/cardaction.vue","./src/components/ui/card/cardcontent.vue","./src/components/ui/card/carddescription.vue","./src/components/ui/card/cardfooter.vue","./src/components/ui/card/cardheader.vue","./src/components/ui/card/cardtitle.vue","./src/components/ui/scroll-area/scrollarea.vue","./src/components/ui/scroll-area/scrollbar.vue","./src/components/ui/tooltip/tooltipcontent.vue","./src/components/views/homeentryview.vue","./src/components/views/ht.vue","./src/components/views/htadditionalworkfee.vue","./src/components/views/htconsultcategoryfactor.vue","./src/components/views/htfeemethodtypelineview.vue","./src/components/views/htfeeratemethodform.vue","./src/components/views/htmajorfactor.vue","./src/components/views/htreservefee.vue","./src/components/views/projectworkspaceview.vue","./src/components/views/quickcalcview.vue","./src/components/views/servicecheckboxselector.vue","./src/components/views/workcontentgrid.vue","./src/components/views/xmconsultcategoryfactor.vue","./src/components/views/xmmajorfactor.vue","./src/components/views/zxfwview.vue","./src/components/views/htcard.vue","./src/components/views/htinfo.vue","./src/components/views/info.vue","./src/components/views/xmcard.vue","./src/components/views/xminfo.vue","./src/components/views/zxfw.vue","./src/components/views/pricingview/hourlypricingpane.vue","./src/components/views/pricingview/investmentscalepricingpane.vue","./src/components/views/pricingview/landscalepricingpane.vue","./src/components/views/pricingview/workloadpricingpane.vue","./src/layout/tab.vue","./src/layout/typeline.vue"],"version":"5.9.3"}
\ No newline at end of file
+{"root":["./src/main.ts","./src/sql.ts","./src/components/ui/button/index.ts","./src/components/ui/card/index.ts","./src/components/ui/scroll-area/index.ts","./src/components/ui/tooltip/index.ts","./src/lib/decimal.ts","./src/lib/diyaggridoptions.ts","./src/lib/number.ts","./src/lib/numberformat.ts","./src/lib/pricingmethodtotals.ts","./src/lib/pricingscalefee.ts","./src/lib/projectworkspace.ts","./src/lib/utils.ts","./src/lib/workspace.ts","./src/lib/xmfactordefaults.ts","./src/lib/zwarchive.ts","./src/lib/zxfwpricingsync.ts","./src/pinia/kv.ts","./src/pinia/tab.ts","./src/pinia/zxfwpricing.ts","./src/pinia/plugin/indexdb.ts","./src/pinia/plugin/types.d.ts","./src/app.vue","./src/components/ht/ht.vue","./src/components/ht/htadditionalworkfee.vue","./src/components/ht/htconsultcategoryfactor.vue","./src/components/ht/htfeeratemethodform.vue","./src/components/ht/htmajorfactor.vue","./src/components/ht/htreservefee.vue","./src/components/ht/htcard.vue","./src/components/ht/htinfo.vue","./src/components/ht/zxfw.vue","./src/components/pricing/hourlypricingpane.vue","./src/components/pricing/investmentscalepricingpane.vue","./src/components/pricing/landscalepricingpane.vue","./src/components/pricing/workloadpricingpane.vue","./src/components/shared/hourlyfeegrid.vue","./src/components/shared/htfeegrid.vue","./src/components/shared/htfeemethodgrid.vue","./src/components/shared/methodunavailablenotice.vue","./src/components/shared/servicecheckboxselector.vue","./src/components/shared/workcontentgrid.vue","./src/components/shared/xmfactorgrid.vue","./src/components/shared/xmcommonaggrid.vue","./src/components/ui/button/button.vue","./src/components/ui/card/card.vue","./src/components/ui/card/cardaction.vue","./src/components/ui/card/cardcontent.vue","./src/components/ui/card/carddescription.vue","./src/components/ui/card/cardfooter.vue","./src/components/ui/card/cardheader.vue","./src/components/ui/card/cardtitle.vue","./src/components/ui/scroll-area/scrollarea.vue","./src/components/ui/scroll-area/scrollbar.vue","./src/components/ui/tooltip/tooltipcontent.vue","./src/components/views/homeentryview.vue","./src/components/views/htfeemethodtypelineview.vue","./src/components/views/projectworkspaceview.vue","./src/components/views/quickcalcview.vue","./src/components/views/zxfwview.vue","./src/components/xm/xmconsultcategoryfactor.vue","./src/components/xm/xmmajorfactor.vue","./src/components/xm/info.vue","./src/components/xm/xmcard.vue","./src/components/xm/xminfo.vue","./src/layout/tab.vue","./src/layout/typeline.vue"],"version":"5.9.3"}
\ No newline at end of file