1
This commit is contained in:
1 parent
d5cae61861
commit
eb415cd0d0
33 files changed
+2642
-382
No files matched your search
@@ -0,0 +1,466 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useTabStore } from '@/pinia/tab'
|
||||
import { useKvStore } from '@/pinia/kv'
|
||||
import { Calculator, Check, ChevronDown, Download, FolderKanban, X, Zap } from 'lucide-vue-next'
|
||||
import { industryTypeList } from '@/sql'
|
||||
import { initializeProjectFactorStates } from '@/lib/projectWorkspace'
|
||||
import {
|
||||
SelectContent,
|
||||
SelectIcon,
|
||||
SelectItem,
|
||||
SelectItemIndicator,
|
||||
SelectItemText,
|
||||
SelectPortal,
|
||||
SelectRoot,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
SelectViewport
|
||||
} from 'reka-ui'
|
||||
import {
|
||||
PROJECT_TAB_ID,
|
||||
QUICK_CONSULT_CATEGORY_FACTOR_KEY,
|
||||
QUICK_CONTRACT_FALLBACK_NAME,
|
||||
QUICK_CONTRACT_ID,
|
||||
QUICK_CONTRACT_META_KEY,
|
||||
QUICK_MAJOR_FACTOR_KEY,
|
||||
QUICK_PROJECT_INFO_KEY,
|
||||
writeWorkspaceMode
|
||||
} from '@/lib/workspace'
|
||||
|
||||
interface QuickProjectInfoState {
|
||||
projectIndustry?: string
|
||||
projectName?: string
|
||||
preparedBy?: string
|
||||
reviewedBy?: string
|
||||
preparedCompany?: string
|
||||
preparedDate?: string
|
||||
}
|
||||
|
||||
interface QuickContractMetaState {
|
||||
id?: string
|
||||
name?: string
|
||||
updatedAt?: string
|
||||
}
|
||||
|
||||
interface ProjectInfoState {
|
||||
projectIndustry?: string
|
||||
projectName?: string
|
||||
preparedBy?: string
|
||||
reviewedBy?: string
|
||||
preparedCompany?: string
|
||||
preparedDate?: string
|
||||
}
|
||||
|
||||
const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
||||
const PROJECT_CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
|
||||
const PROJECT_MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
||||
const DEFAULT_PROJECT_NAME = 'xxx造价咨询服务'
|
||||
const PROJECT_INIT_CHANGED_EVENT = 'xm-project-init-changed'
|
||||
|
||||
const tabStore = useTabStore()
|
||||
const kvStore = useKvStore()
|
||||
const projectDialogOpen = ref(false)
|
||||
const projectIndustry = ref(String(industryTypeList[0]?.id || ''))
|
||||
const projectSubmitting = ref(false)
|
||||
const quickDialogOpen = ref(false)
|
||||
const quickIndustry = ref(String(industryTypeList[0]?.id || ''))
|
||||
const quickContractName = ref(QUICK_CONTRACT_FALLBACK_NAME)
|
||||
const quickSubmitting = ref(false)
|
||||
|
||||
const getTodayDateString = () => {
|
||||
const now = new Date()
|
||||
const year = String(now.getFullYear())
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(now.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
const enterProjectCalc = () => {
|
||||
writeWorkspaceMode('project')
|
||||
tabStore.enterWorkspace({
|
||||
id: PROJECT_TAB_ID,
|
||||
title: '项目计算',
|
||||
componentName: PROJECT_TAB_ID
|
||||
})
|
||||
}
|
||||
|
||||
const loadProjectDefaults = async () => {
|
||||
const savedInfo = await kvStore.getItem<ProjectInfoState>(PROJECT_INFO_KEY)
|
||||
projectIndustry.value =
|
||||
typeof savedInfo?.projectIndustry === 'string' && savedInfo.projectIndustry.trim()
|
||||
? savedInfo.projectIndustry.trim()
|
||||
: String(industryTypeList[0]?.id || '')
|
||||
}
|
||||
|
||||
const openProjectCalc = async () => {
|
||||
const savedInfo = await kvStore.getItem<ProjectInfoState>(PROJECT_INFO_KEY)
|
||||
if (typeof savedInfo?.projectIndustry === 'string' && savedInfo.projectIndustry.trim()) {
|
||||
enterProjectCalc()
|
||||
return
|
||||
}
|
||||
await loadProjectDefaults()
|
||||
projectDialogOpen.value = true
|
||||
}
|
||||
|
||||
const closeProjectCalcDialog = () => {
|
||||
projectDialogOpen.value = false
|
||||
}
|
||||
|
||||
const confirmProjectCalc = async () => {
|
||||
const industry = projectIndustry.value.trim()
|
||||
if (!industry) return
|
||||
|
||||
projectSubmitting.value = true
|
||||
try {
|
||||
await kvStore.setItem<ProjectInfoState>(PROJECT_INFO_KEY, {
|
||||
projectIndustry: industry,
|
||||
projectName: DEFAULT_PROJECT_NAME,
|
||||
preparedBy: '',
|
||||
reviewedBy: '',
|
||||
preparedCompany: '',
|
||||
preparedDate: getTodayDateString()
|
||||
})
|
||||
await initializeProjectFactorStates(
|
||||
kvStore,
|
||||
industry,
|
||||
PROJECT_CONSULT_CATEGORY_FACTOR_KEY,
|
||||
PROJECT_MAJOR_FACTOR_KEY
|
||||
)
|
||||
window.dispatchEvent(new CustomEvent<boolean>(PROJECT_INIT_CHANGED_EVENT, { detail: true }))
|
||||
enterProjectCalc()
|
||||
} finally {
|
||||
projectSubmitting.value = false
|
||||
projectDialogOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const loadQuickDefaults = async () => {
|
||||
const [savedInfo, savedMeta] = await Promise.all([
|
||||
kvStore.getItem<QuickProjectInfoState>(QUICK_PROJECT_INFO_KEY),
|
||||
kvStore.getItem<QuickContractMetaState>(QUICK_CONTRACT_META_KEY)
|
||||
])
|
||||
|
||||
quickIndustry.value =
|
||||
typeof savedInfo?.projectIndustry === 'string' && savedInfo.projectIndustry.trim()
|
||||
? savedInfo.projectIndustry.trim()
|
||||
: String(industryTypeList[0]?.id || '')
|
||||
quickContractName.value =
|
||||
typeof savedMeta?.name === 'string' && savedMeta.name.trim()
|
||||
? savedMeta.name.trim()
|
||||
: QUICK_CONTRACT_FALLBACK_NAME
|
||||
}
|
||||
|
||||
const openQuickCalcDialog = async () => {
|
||||
await loadQuickDefaults()
|
||||
quickDialogOpen.value = true
|
||||
}
|
||||
|
||||
const closeQuickCalcDialog = () => {
|
||||
quickDialogOpen.value = false
|
||||
}
|
||||
|
||||
const confirmQuickCalc = async () => {
|
||||
const contractName = quickContractName.value.trim()
|
||||
const industry = quickIndustry.value.trim()
|
||||
if (!contractName || !industry) return
|
||||
|
||||
quickSubmitting.value = true
|
||||
try {
|
||||
const currentInfo = await kvStore.getItem<QuickProjectInfoState>(QUICK_PROJECT_INFO_KEY)
|
||||
await kvStore.setItem(QUICK_PROJECT_INFO_KEY, {
|
||||
...currentInfo,
|
||||
projectIndustry: industry,
|
||||
projectName: '快速计算'
|
||||
})
|
||||
await kvStore.setItem(QUICK_CONTRACT_META_KEY, {
|
||||
id: QUICK_CONTRACT_ID,
|
||||
name: contractName,
|
||||
updatedAt: new Date().toISOString()
|
||||
})
|
||||
await initializeProjectFactorStates(
|
||||
kvStore,
|
||||
industry,
|
||||
QUICK_CONSULT_CATEGORY_FACTOR_KEY,
|
||||
QUICK_MAJOR_FACTOR_KEY
|
||||
)
|
||||
|
||||
writeWorkspaceMode('quick')
|
||||
tabStore.enterWorkspace({
|
||||
id: `contract-${QUICK_CONTRACT_ID}`,
|
||||
title: contractName,
|
||||
componentName: 'ContractDetailView',
|
||||
props: {
|
||||
contractId: QUICK_CONTRACT_ID,
|
||||
contractName,
|
||||
projectInfoKey: QUICK_PROJECT_INFO_KEY,
|
||||
projectScaleKey: null,
|
||||
projectConsultCategoryFactorKey: QUICK_CONSULT_CATEGORY_FACTOR_KEY,
|
||||
projectMajorFactorKey: QUICK_MAJOR_FACTOR_KEY
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
quickSubmitting.value = false
|
||||
quickDialogOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleHomeImportChange = (event: Event) => {
|
||||
const input = event.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
if (!file) return
|
||||
window.dispatchEvent(new CustomEvent('home-import-selected', {
|
||||
detail: {
|
||||
file
|
||||
}
|
||||
}))
|
||||
input.value = ''
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
void loadProjectDefaults()
|
||||
void loadQuickDefaults()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input id="home-import-input" type="file" accept=".zw" class="sr-only" @change="handleHomeImportChange" />
|
||||
<div class="flex min-h-full items-center justify-center px-4 py-8">
|
||||
<div class="w-full max-w-5xl">
|
||||
<div class="mx-auto max-w-2xl text-center">
|
||||
<p class="text-sm font-medium tracking-[0.28em] text-muted-foreground">JGJS 2026</p>
|
||||
<h1 class="mt-4 text-4xl font-semibold tracking-tight text-foreground">选择计算入口</h1>
|
||||
<p class="mt-3 text-sm leading-6 text-muted-foreground">
|
||||
首页支持三种入口:继续项目计算、快速单合同测算,或直接导入已有数据包。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-10 grid items-stretch gap-5 md:grid-cols-3">
|
||||
<Card
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class="flex h-full cursor-pointer flex-col border-border/70 bg-card/90 shadow-[0_18px_48px_rgba(15,23,42,0.08)] transition hover:-translate-y-0.5 hover:shadow-[0_22px_56px_rgba(15,23,42,0.12)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
@click="openProjectCalc"
|
||||
@keydown.enter.prevent="openProjectCalc"
|
||||
@keydown.space.prevent="openProjectCalc"
|
||||
>
|
||||
<CardHeader class="flex min-h-[184px] flex-col space-y-4 pb-4">
|
||||
<div class="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-slate-900 text-white">
|
||||
<FolderKanban class="h-6 w-6" />
|
||||
</div>
|
||||
<div class="min-h-[88px]">
|
||||
<CardTitle class="text-2xl">项目计算</CardTitle>
|
||||
<CardDescription class="mt-2 text-sm leading-6">
|
||||
继续使用当前项目卡片、流程线、合同段管理和整项目导入导出能力。
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="mt-auto flex flex-1 flex-col justify-end space-y-4">
|
||||
<div class="flex min-h-[72px] items-center rounded-2xl border border-border/60 bg-muted/35 p-4 text-sm text-muted-foreground">
|
||||
适合多合同段、项目级系数维护、整项目报表和批量导入导出。
|
||||
</div>
|
||||
<Button class="w-full justify-between" @click.stop="openProjectCalc">
|
||||
进入项目计算
|
||||
<Calculator class="h-4 w-4" />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
role="button"
|
||||
tabindex="0"
|
||||
class="flex h-full cursor-pointer flex-col border-border/70 bg-[linear-gradient(135deg,rgba(15,23,42,0.05),rgba(148,163,184,0.12))] shadow-[0_18px_48px_rgba(15,23,42,0.08)] transition hover:-translate-y-0.5 hover:shadow-[0_22px_56px_rgba(15,23,42,0.12)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
@click="openQuickCalcDialog"
|
||||
@keydown.enter.prevent="openQuickCalcDialog"
|
||||
@keydown.space.prevent="openQuickCalcDialog"
|
||||
>
|
||||
<CardHeader class="flex min-h-[184px] flex-col space-y-4 pb-4">
|
||||
<div class="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-amber-500 text-slate-950">
|
||||
<Zap class="h-6 w-6" />
|
||||
</div>
|
||||
<div class="min-h-[88px]">
|
||||
<CardTitle class="text-2xl">快速计算</CardTitle>
|
||||
<CardDescription class="mt-2 text-sm leading-6">
|
||||
先填写工程行业和合同名称,再直接进入单合同预算费用测算页面。
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="mt-auto flex flex-1 flex-col justify-end space-y-4">
|
||||
<div class="flex min-h-[72px] items-center rounded-2xl border border-border/60 bg-background/80 p-4 text-sm text-muted-foreground">
|
||||
适合快速试算、单合同预算复核和不需要项目级合同段管理的场景。
|
||||
</div>
|
||||
<Button variant="outline" class="w-full justify-between" @click.stop="openQuickCalcDialog">
|
||||
进入快速计算
|
||||
<Zap class="h-4 w-4" />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<label for="home-import-input" class="block h-full cursor-pointer">
|
||||
<Card
|
||||
class="flex h-full flex-col border-border/70 bg-[linear-gradient(135deg,rgba(14,116,144,0.08),rgba(186,230,253,0.26))] shadow-[0_18px_48px_rgba(15,23,42,0.08)] transition hover:-translate-y-0.5 hover:shadow-[0_22px_56px_rgba(15,23,42,0.12)]"
|
||||
>
|
||||
<CardHeader class="flex min-h-[184px] flex-col space-y-4 pb-4">
|
||||
<div class="inline-flex h-12 w-12 items-center justify-center rounded-2xl bg-cyan-600 text-white">
|
||||
<Download class="h-6 w-6" />
|
||||
</div>
|
||||
<div class="min-h-[88px]">
|
||||
<CardTitle class="text-2xl">导入数据</CardTitle>
|
||||
<CardDescription class="mt-2 text-sm leading-6">
|
||||
直接导入已有 `.zw` 数据包,恢复项目计算或快速计算的本地工作区状态。
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent class="mt-auto flex flex-1 flex-col justify-end space-y-4">
|
||||
<div class="flex min-h-[72px] items-center rounded-2xl border border-border/60 bg-background/80 p-4 text-sm text-muted-foreground">
|
||||
适合继续上次工作、切换设备恢复数据,或直接打开别人发来的测算包。
|
||||
</div>
|
||||
<div class="inline-flex h-10 w-full items-center justify-between rounded-md border border-input bg-secondary px-4 text-sm font-medium text-secondary-foreground shadow-xs transition-colors">
|
||||
<span>选择导入文件</span>
|
||||
<Download class="h-4 w-4" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="projectDialogOpen"
|
||||
class="fixed inset-0 z-[90] flex items-center justify-center bg-black/45 p-4"
|
||||
@click.self="closeProjectCalcDialog"
|
||||
>
|
||||
<div class="w-full max-w-md rounded-xl border bg-background shadow-2xl">
|
||||
<div class="flex items-center justify-between border-b px-5 py-4">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-foreground">新建项目</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">选择工程行业后,直接进入项目计算页面。</p>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" @click="closeProjectCalcDialog">
|
||||
<X class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4 px-5 py-4">
|
||||
<label class="block space-y-2">
|
||||
<span class="text-sm font-medium text-foreground">工程行业</span>
|
||||
<SelectRoot v-model="projectIndustry">
|
||||
<SelectTrigger
|
||||
class="inline-flex h-11 w-full items-center justify-between rounded-xl border border-border/70 bg-[linear-gradient(180deg,rgba(248,250,252,0.98),rgba(241,245,249,0.92))] px-4 text-sm text-foreground shadow-sm outline-none transition hover:border-border focus-visible:ring-2 focus-visible:ring-slate-300"
|
||||
>
|
||||
<SelectValue placeholder="请选择工程行业" />
|
||||
<SelectIcon as-child>
|
||||
<ChevronDown class="h-4 w-4 text-muted-foreground" />
|
||||
</SelectIcon>
|
||||
</SelectTrigger>
|
||||
<SelectPortal>
|
||||
<SelectContent
|
||||
:side-offset="6"
|
||||
position="popper"
|
||||
class="z-[120] w-[var(--reka-select-trigger-width)] overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-xl"
|
||||
>
|
||||
<SelectViewport class="p-1">
|
||||
<SelectItem
|
||||
v-for="item in industryTypeList"
|
||||
:key="`project-${item.id}`"
|
||||
:value="String(item.id)"
|
||||
class="relative flex h-9 w-full cursor-default select-none items-center rounded-md pl-3 pr-8 text-sm outline-none data-[highlighted]:bg-muted data-[highlighted]:text-foreground data-[state=checked]:bg-slate-100"
|
||||
>
|
||||
<SelectItemText>{{ item.name }}</SelectItemText>
|
||||
<SelectItemIndicator class="absolute right-2 inline-flex items-center text-slate-700">
|
||||
<Check class="h-4 w-4" />
|
||||
</SelectItemIndicator>
|
||||
</SelectItem>
|
||||
</SelectViewport>
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
</SelectRoot>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-2 border-t px-5 py-4">
|
||||
<Button variant="outline" @click="closeProjectCalcDialog">取消</Button>
|
||||
<Button :disabled="projectSubmitting || !projectIndustry" @click="confirmProjectCalc">
|
||||
{{ projectSubmitting ? '进入中...' : '进入项目计算' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="quickDialogOpen"
|
||||
class="fixed inset-0 z-[90] flex items-center justify-center bg-black/45 p-4"
|
||||
@click.self="closeQuickCalcDialog"
|
||||
>
|
||||
<div class="w-full max-w-md rounded-xl border bg-background shadow-2xl">
|
||||
<div class="flex items-center justify-between border-b px-5 py-4">
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-foreground">快速计算</h3>
|
||||
<p class="mt-1 text-sm text-muted-foreground">填写工程行业和合同名称后,直接进入单合同计算页面。</p>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" @click="closeQuickCalcDialog">
|
||||
<X class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4 px-5 py-4">
|
||||
<label class="block space-y-2">
|
||||
<span class="text-sm font-medium text-foreground">工程行业</span>
|
||||
<SelectRoot v-model="quickIndustry">
|
||||
<SelectTrigger
|
||||
class="inline-flex h-11 w-full items-center justify-between rounded-xl border border-border/70 bg-[linear-gradient(180deg,rgba(248,250,252,0.98),rgba(241,245,249,0.92))] px-4 text-sm text-foreground shadow-sm outline-none transition hover:border-border focus-visible:ring-2 focus-visible:ring-slate-300"
|
||||
>
|
||||
<SelectValue placeholder="请选择工程行业" />
|
||||
<SelectIcon as-child>
|
||||
<ChevronDown class="h-4 w-4 text-muted-foreground" />
|
||||
</SelectIcon>
|
||||
</SelectTrigger>
|
||||
<SelectPortal>
|
||||
<SelectContent
|
||||
:side-offset="6"
|
||||
position="popper"
|
||||
class="z-[120] w-[var(--reka-select-trigger-width)] overflow-hidden rounded-xl border border-border bg-popover text-popover-foreground shadow-xl"
|
||||
>
|
||||
<SelectViewport class="p-1">
|
||||
<SelectItem
|
||||
v-for="item in industryTypeList"
|
||||
:key="`quick-${item.id}`"
|
||||
:value="String(item.id)"
|
||||
class="relative flex h-9 w-full cursor-default select-none items-center rounded-md pl-3 pr-8 text-sm outline-none data-[highlighted]:bg-muted data-[highlighted]:text-foreground data-[state=checked]:bg-slate-100"
|
||||
>
|
||||
<SelectItemText>{{ item.name }}</SelectItemText>
|
||||
<SelectItemIndicator class="absolute right-2 inline-flex items-center text-slate-700">
|
||||
<Check class="h-4 w-4" />
|
||||
</SelectItemIndicator>
|
||||
</SelectItem>
|
||||
</SelectViewport>
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
</SelectRoot>
|
||||
</label>
|
||||
|
||||
<label class="block space-y-2">
|
||||
<span class="text-sm font-medium text-foreground">合同名称</span>
|
||||
<input
|
||||
v-model="quickContractName"
|
||||
type="text"
|
||||
maxlength="40"
|
||||
placeholder="请输入合同名称"
|
||||
class="h-10 w-full rounded-md border bg-background px-3 text-sm outline-none transition focus-visible:ring-2 focus-visible:ring-ring"
|
||||
@keydown.enter="confirmQuickCalc"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end gap-2 border-t px-5 py-4">
|
||||
<Button variant="outline" @click="closeQuickCalcDialog">取消</Button>
|
||||
<Button :disabled="quickSubmitting || !quickIndustry || !quickContractName.trim()" @click="confirmQuickCalc">
|
||||
{{ quickSubmitting ? '进入中...' : '进入快速计算' }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in new issue
Block a user