From 69e5f166e139bb459f969b37facb9b0fd1420885 Mon Sep 17 00:00:00 2001 From: wintsa <770775984@qq.com> Date: Tue, 17 Mar 2026 16:24:25 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 4 ++- src/components/ht/HtAdditionalWorkFee.vue | 2 +- src/components/ht/HtReserveFee.vue | 2 +- src/components/ht/zxFw.vue | 6 ++--- .../pricing/InvestmentScalePricingPane.vue | 6 ++--- .../pricing/LandScalePricingPane.vue | 6 ++--- .../pricing/WorkloadPricingPane.vue | 6 ++--- src/components/shared/HourlyFeeGrid.vue | 6 ++--- src/components/shared/HtFeeGrid.vue | 6 ++--- src/components/shared/HtFeeMethodGrid.vue | 26 ++++++++++--------- src/components/shared/WorkContentGrid.vue | 19 +++++++++----- src/components/shared/XmFactorGrid.vue | 6 ++--- src/components/shared/xmCommonAgGrid.vue | 6 ++--- .../views/HtFeeMethodTypeLineView.vue | 6 +---- src/layout/tab.vue | 9 ++----- src/layout/typeLine.vue | 5 +--- src/lib/diyAgGridOptions.ts | 6 +++++ src/sql.ts | 5 ++-- src/style.css | 1 + tsconfig.tsbuildinfo | 2 +- 20 files changed, 70 insertions(+), 65 deletions(-) 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