diff --git a/src/components/ht/Ht.vue b/src/components/ht/Ht.vue index 8297a4b..7023568 100644 --- a/src/components/ht/Ht.vue +++ b/src/components/ht/Ht.vue @@ -1212,7 +1212,7 @@ const handleCardClick = (item: ContractItem) => { tabStore.openTab({ id: `contract-${item.id}`, title: `合同段${item.name}`, - componentName: 'ContractDetailView', + componentName: 'QuickCalcView', props: { contractId: item.id, contractName: item.name } }) } diff --git a/src/components/ht/HtBaseInfo.vue b/src/components/ht/HtBaseInfo.vue new file mode 100644 index 0000000..aa6f71d --- /dev/null +++ b/src/components/ht/HtBaseInfo.vue @@ -0,0 +1,102 @@ + + + + + + + 基础信息 + + + + 质量要求 + + + + + 工期要求 + + + + + + + + \ No newline at end of file diff --git a/src/components/ht/htCard.vue b/src/components/ht/htCard.vue index 0357475..c1225cb 100644 --- a/src/components/ht/htCard.vue +++ b/src/components/ht/htCard.vue @@ -182,6 +182,7 @@ const scheduleRefreshContractBudget = () => { interface XmCategoryItem { key: | 'info' + | 'base-info' | 'consult-category-factor' | 'major-factor' | 'work-grid' @@ -271,6 +272,21 @@ const majorFactorView = markRaw( +const htBaseInfoView = markRaw( + defineComponent({ + name: 'HtBaseInfoWithProps', + setup() { + const AsyncHtBaseInfo = defineAsyncComponent({ + loader: () => import('@/components/ht/HtBaseInfo.vue'), + onError: (err) => { + console.error('加载 HtBaseInfo 组件失败:', err) + } + }) + return () => h(AsyncHtBaseInfo, { contractId: props.contractId }) + } + }) +) + const additionalWorkFeeView = markRaw( defineComponent({ name: 'HtAdditionalWorkFeeWithProps', @@ -303,6 +319,7 @@ const reserveFeeView = markRaw( // 4. 给分类数组添加严格类型标注 const xmCategories: XmCategoryItem[] = [ + { key: 'base-info', label: '基础信息', component: htBaseInfoView }, { key: 'info', label: '规模信息', component: htView }, { key: 'consult-category-factor', label: '咨询分类系数', component: consultCategoryFactorView }, { key: 'major-factor', label: '工程专业系数', component: majorFactorView }, diff --git a/src/components/shared/WorkContentGrid.vue b/src/components/shared/WorkContentGrid.vue index 0d92ffa..c795a2a 100644 --- a/src/components/shared/WorkContentGrid.vue +++ b/src/components/shared/WorkContentGrid.vue @@ -1,5 +1,5 @@ @@ -291,6 +368,25 @@ onBeforeUnmount(() => { /> + + + + + 确认删除行 + + 将删除“{{ pendingDeleteRowName }}”这条明细,是否继续? + + + + 取消 + + + 确认删除 + + + + + diff --git a/src/components/views/HomeEntryView.vue b/src/components/views/HomeEntryView.vue index 0746dac..3852596 100644 --- a/src/components/views/HomeEntryView.vue +++ b/src/components/views/HomeEntryView.vue @@ -95,7 +95,7 @@ const enterProjectCalc = () => { tabStore.enterWorkspace({ id: PROJECT_TAB_ID, title: '项目计算', - componentName: 'XmView' + componentName: 'ProjectCalcView' }) tabStore.hasCompletedSetup = true } @@ -109,12 +109,7 @@ const loadProjectDefaults = async () => { } const openProjectCalc = async () => { - const savedInfo = await kvStore.getItem(PROJECT_INFO_KEY) - if (typeof savedInfo?.projectIndustry === 'string' && savedInfo.projectIndustry.trim()) { - enterProjectCalc() - return - } - await loadProjectDefaults() + projectDialogOpen.value = true } @@ -200,10 +195,11 @@ const confirmQuickCalc = async () => { QUICK_MAJOR_FACTOR_KEY ) + writeWorkspaceMode('quick') tabStore.enterWorkspace({ id: `contract-${QUICK_CONTRACT_ID}`, title: contractName, - componentName: 'ContractDetailView', + componentName: 'QuickCalcView', props: { contractId: QUICK_CONTRACT_ID, contractName, diff --git a/src/components/views/ProjectWorkspaceView.vue b/src/components/views/ProjectWorkspaceView.vue deleted file mode 100644 index dfe9b26..0000000 --- a/src/components/views/ProjectWorkspaceView.vue +++ /dev/null @@ -1,17 +0,0 @@ - - - - - diff --git a/src/components/views/QuickCalcView.vue b/src/components/views/QuickCalcView.vue deleted file mode 100644 index fa84aba..0000000 --- a/src/components/views/QuickCalcView.vue +++ /dev/null @@ -1,413 +0,0 @@ - - - - - - - - - - 快速计算 - - - 保留一个默认合同卡片,不再经过项目卡片入口,直接进入单合同预算费用计算。 - - - - - 工程行业 - - - {{ item.name }} - - - - - 合同名称 - - - - - - - - - - - 当前状态 - - - 模式:单合同快速计算 - 合同ID:{{ QUICK_CONTRACT_ID }} - 行业切换会同步重建快速计算专用系数基线 - {{ savingIndustry ? '正在切换行业并刷新系数...' : '行业与合同名称已自动保存' }} - - - - - - - - - {{ contractName.trim() || QUICK_CONTRACT_FALLBACK_NAME }} - - 默认单合同卡片,点击后进入预算费用计算详情。 - - - 进入计算 - - - - - - 合同ID - {{ QUICK_CONTRACT_ID }} - - - 预算费用 - {{ formatBudgetAmount(quickBudget) }} - - - 行业 - - {{ availableIndustries.find(item => item.id === projectIndustry)?.name || '--' }} - - - - - - diff --git a/src/layout/tab.vue b/src/layout/tab.vue index d6859f5..d0fed08 100644 --- a/src/layout/tab.vue +++ b/src/layout/tab.vue @@ -22,6 +22,7 @@ import { AlertDialogTrigger, } from 'reka-ui' import { decodeZwArchive, encodeZwArchive, ZW_FILE_EXTENSION } from '@/lib/zwArchive' +import { PROJECT_TAB_ID, QUICK_TAB_ID, readWorkspaceMode } from '@/lib/workspace' import { addNumbers, roundTo } from '@/lib/decimal' import { exportFile, serviceList } from '@/sql' @@ -431,8 +432,8 @@ const userGuideSteps: UserGuideStep[] = [ ] const componentMap: Record = { - XmView: markRaw(defineAsyncComponent(() => import('@/components/xm/xmCard.vue'))), - ContractDetailView: markRaw(defineAsyncComponent(() => import('@/components/ht/htCard.vue'))), + ProjectCalcView: markRaw(defineAsyncComponent(() => import('@/components/xm/xmCard.vue'))), + QuickCalcView: markRaw(defineAsyncComponent(() => import('@/components/ht/htCard.vue'))), ZxFwView: markRaw(defineAsyncComponent(() => import('@/components/views/ZxFwView.vue'))), HtFeeMethodTypeLineView: markRaw(defineAsyncComponent(() => import('@/components/views/HtFeeMethodTypeLineView.vue'))), } @@ -446,7 +447,7 @@ const kvStore = useKvStore() const tabContextOpen = ref(false) const tabContextX = ref(0) const tabContextY = ref(0) -const contextTabId = ref('XmView') +const contextTabId = ref('ProjectCalcView') const tabContextRef = ref(null) const dataMenuOpen = ref(false) @@ -478,7 +479,11 @@ const tabsModel = computed({ const contextTabIndex = computed(() => tabStore.tabs.findIndex((t:any) => t.id === contextTabId.value)) -const hasClosableTabs = computed(() => tabStore.tabs.some((t:any) => t.id !== 'XmView')) +const hasClosableTabs = computed(() => { + const fixedId = readWorkspaceMode() === 'quick' ? QUICK_TAB_ID : PROJECT_TAB_ID + + return tabStore.tabs.some((t: any) => t.componentName !== fixedId) +}) const activeGuideStep = computed( () => userGuideSteps[userGuideStepIndex.value] || userGuideSteps[0] ) @@ -494,7 +499,7 @@ const canCloseRight = computed(() => { return tabStore.tabs.slice(contextTabIndex.value + 1).length > 0 }) const canCloseOther = computed(() => - tabStore.tabs.length > 1 && contextTabIndex.value !== 0 + tabStore.tabs.slice(1, contextTabIndex.value).length > 1 && contextTabIndex.value !== 0 ) const closeMenus = () => { @@ -525,9 +530,9 @@ const hasNonDefaultTabState = () => { if (!raw) return false const parsed = JSON.parse(raw) as { tabs?: Array<{ id?: string }>; activeTabId?: string } const tabs = Array.isArray(parsed?.tabs) ? parsed.tabs : [] - const hasCustomTabs = tabs.some(item => item?.id && item.id !== 'XmView') + const hasCustomTabs = tabs.some(item => item?.id && item.id !== 'ProjectCalcView') const activeTabId = typeof parsed?.activeTabId === 'string' ? parsed.activeTabId : '' - return hasCustomTabs || (activeTabId !== '' && activeTabId !== 'XmView') + return hasCustomTabs || (activeTabId !== '' && activeTabId !== 'ProjectCalcView') } catch (error) { console.error('parse tabs cache failed:', error) return false @@ -1827,7 +1832,8 @@ watch( 导出 + v-if="readWorkspaceMode() !== 'quick'" + @click="exportReport"> 导出报表 diff --git a/src/lib/workspace.ts b/src/lib/workspace.ts index 2a7234f..c1d86b6 100644 --- a/src/lib/workspace.ts +++ b/src/lib/workspace.ts @@ -1,8 +1,8 @@ -export type WorkspaceMode = 'home' | 'project' | 'quick' +export type WorkspaceMode = 'project' | 'quick' export const PROJECT_TAB_ID = 'ProjectCalcView' export const QUICK_TAB_ID = 'QuickCalcView' -export const LEGACY_PROJECT_TAB_ID = 'XmView' +export const LEGACY_PROJECT_TAB_ID = 'ProjectCalcView' export const FIXED_WORKSPACE_TAB_IDS = [PROJECT_TAB_ID, QUICK_TAB_ID] as const export const WORKSPACE_MODE_STORAGE_KEY = 'jgjs-workspace-mode-v1' @@ -23,16 +23,13 @@ export interface QuickContractMeta { updatedAt: string } -export const normalizeWorkspaceMode = (value: unknown): WorkspaceMode => { - if (value === 'project' || value === 'quick' || value === 'home') return value - return 'home' -} + export const readWorkspaceMode = (): WorkspaceMode => { try { - return normalizeWorkspaceMode(window.localStorage.getItem(WORKSPACE_MODE_STORAGE_KEY)) + return window.localStorage.getItem(WORKSPACE_MODE_STORAGE_KEY) as WorkspaceMode } catch { - return 'home' + return 'project' } } @@ -45,7 +42,7 @@ export const createDefaultQuickContractMeta = (): QuickContractMeta => ({ }) export const writeWorkspaceMode = (mode: WorkspaceMode) => { try { - window.localStorage.setItem(WORKSPACE_MODE_STORAGE_KEY, normalizeWorkspaceMode(mode)) + window.localStorage.setItem(WORKSPACE_MODE_STORAGE_KEY, mode) } catch { // 忽略只读或隐私模式下的写入失败。 } diff --git a/src/pinia/tab.ts b/src/pinia/tab.ts index ec3205b..ba442d3 100644 --- a/src/pinia/tab.ts +++ b/src/pinia/tab.ts @@ -1,6 +1,11 @@ import { defineStore } from 'pinia' import { ref } from 'vue' -import { PROJECT_TAB_ID, QUICK_CONTRACT_TAB_ID } from '@/lib/workspace' +import { + PROJECT_TAB_ID, + QUICK_CONTRACT_TAB_ID, + QUICK_TAB_ID, + readWorkspaceMode +} from '@/lib/workspace' export interface TabItem> { id: string @@ -9,32 +14,44 @@ export interface TabItem> { props?: TProps } -const DEFAULT_TAB: TabItem = { +const DEFAULT_PROJECT_TAB: TabItem = { id: PROJECT_TAB_ID, title: '项目卡片', - componentName: 'XmView' + componentName: 'ProjectCalcView' } -const createDefaultTabs = (): TabItem[] => [{...DEFAULT_TAB}] -const PROTECTED_TAB_ID_SET = new Set([ PROJECT_TAB_ID,QUICK_CONTRACT_TAB_ID]) +/** 根据当前 workspace mode 返回受保护的 tab ID 集合 */ +const getProtectedIds = (): Set => { + return readWorkspaceMode() === 'quick' + ? new Set([QUICK_TAB_ID, QUICK_CONTRACT_TAB_ID]) + : new Set([PROJECT_TAB_ID, QUICK_CONTRACT_TAB_ID]) +} + +/** 根据当前 workspace mode 返回首个 tab 的 fallback ID */ +const getFallbackTabId = (): string => { + return readWorkspaceMode() === 'quick' ? QUICK_TAB_ID : PROJECT_TAB_ID +} export const useTabStore = defineStore( 'tabs', () => { - const tabs = ref(createDefaultTabs()) + const tabs = ref([{ ...DEFAULT_PROJECT_TAB }]) const activeTabId = ref() const hasCompletedSetup = ref(false) const ensureHomeTab = () => { - if (tabs.value.some(tab => tab.id === PROJECT_TAB_ID)) return - tabs.value = [...createDefaultTabs(), ...tabs.value] + const fallbackId = getFallbackTabId() + if (tabs.value.some(tab => tab.id === fallbackId)) return + // quick 模式下不自动插入默认 tab,由 enterWorkspace 控制 + if (readWorkspaceMode() === 'quick') return + tabs.value = [{ ...DEFAULT_PROJECT_TAB }, ...tabs.value] } const ensureActiveValid = () => { ensureHomeTab() - if (tabs.value.length === 0) tabs.value = createDefaultTabs() + if (tabs.value.length === 0) tabs.value = [{ ...DEFAULT_PROJECT_TAB }] if (!tabs.value.some(tab => tab.id === activeTabId.value)) { - activeTabId.value = tabs.value[0]?.id ?? PROJECT_TAB_ID + activeTabId.value = tabs.value[0]?.id ?? getFallbackTabId() } } @@ -51,7 +68,7 @@ export const useTabStore = defineStore( } const removeTab = (id: string) => { - if (PROTECTED_TAB_ID_SET.has(id)) return + if (getProtectedIds().has(id)) return const index = tabs.value.findIndex(tab => tab.id === id) if (index < 0) return @@ -62,7 +79,7 @@ export const useTabStore = defineStore( if (wasActive) { const fallbackIndex = Math.max(0, Math.min(index - 1, tabs.value.length - 1)) - activeTabId.value = tabs.value[fallbackIndex]?.id ?? PROJECT_TAB_ID + activeTabId.value = tabs.value[fallbackIndex]?.id ?? getFallbackTabId() return } @@ -70,33 +87,37 @@ export const useTabStore = defineStore( } const closeAllTabs = () => { - const protectedTabs = tabs.value.filter(tab => PROTECTED_TAB_ID_SET.has(tab.id)) - tabs.value = protectedTabs.length > 0 ? protectedTabs : createDefaultTabs() - activeTabId.value = tabs.value[0]?.id ?? PROJECT_TAB_ID + const protectedIds = getProtectedIds() + const protectedTabs = tabs.value.filter(tab => protectedIds.has(tab.id)) + tabs.value = protectedTabs.length > 0 ? protectedTabs : [{ ...DEFAULT_PROJECT_TAB }] + activeTabId.value = tabs.value[0]?.id ?? getFallbackTabId() } const closeLeftTabs = (targetId: string) => { const targetIndex = tabs.value.findIndex(tab => tab.id === targetId) if (targetIndex < 0) return - tabs.value = tabs.value.filter((tab, index) => PROTECTED_TAB_ID_SET.has(tab.id) || index >= targetIndex) + const protectedIds = getProtectedIds() + tabs.value = tabs.value.filter((tab, index) => protectedIds.has(tab.id) || index >= targetIndex) ensureActiveValid() } const closeRightTabs = (targetId: string) => { const targetIndex = tabs.value.findIndex(tab => tab.id === targetId) if (targetIndex < 0) return - tabs.value = tabs.value.filter((tab, index) => PROTECTED_TAB_ID_SET.has(tab.id) || index <= targetIndex) + const protectedIds = getProtectedIds() + tabs.value = tabs.value.filter((tab, index) => protectedIds.has(tab.id) || index <= targetIndex) ensureActiveValid() } const closeOtherTabs = (targetId: string) => { - tabs.value = tabs.value.filter(tab => PROTECTED_TAB_ID_SET.has(tab.id) || tab.id === targetId) + const protectedIds = getProtectedIds() + tabs.value = tabs.value.filter(tab => protectedIds.has(tab.id) || tab.id === targetId) ensureHomeTab() - activeTabId.value = tabs.value.some(tab => tab.id === targetId) ? targetId : PROJECT_TAB_ID + activeTabId.value = tabs.value.some(tab => tab.id === targetId) ? targetId : getFallbackTabId() } const resetTabs = () => { - tabs.value = createDefaultTabs() + tabs.value = [{ ...DEFAULT_PROJECT_TAB }] activeTabId.value = PROJECT_TAB_ID hasCompletedSetup.value = false }