This commit is contained in:
wintsa 2026-03-17 18:27:56 +08:00
parent 1165ee91ce
commit 0f7deb0f1b
4 changed files with 98 additions and 78 deletions

View File

@ -188,7 +188,8 @@ interface XmCategoryItem {
| 'work-grid' | 'work-grid'
| 'contract' | 'contract'
| 'additional-work-fee' | 'additional-work-fee'
| 'reserve-fee'; | 'reserve-fee'
| 'all';
label: string; label: string;
component: Component; // Vue component: Component; // Vue
} }
@ -326,6 +327,7 @@ const xmCategories: XmCategoryItem[] = [
{ key: 'contract', label: '咨询服务', component: zxfwView }, { key: 'contract', label: '咨询服务', component: zxfwView },
{ key: 'additional-work-fee', label: '附加工作费', component: additionalWorkFeeView }, { key: 'additional-work-fee', label: '附加工作费', component: additionalWorkFeeView },
{ key: 'reserve-fee', label: '预备费', component: reserveFeeView }, { key: 'reserve-fee', label: '预备费', component: reserveFeeView },
{ key: 'all', label: '汇总', component: reserveFeeView },
]; ];

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { useTabStore } from '@/pinia/tab' import { useTabStore } from '@/pinia/tab'
import { useKvStore } from '@/pinia/kv' import { useKvStore } from '@/pinia/kv'
@ -240,129 +240,127 @@ onMounted(() => {
<template> <template>
<input ref="homeImportInputRef" type="file" accept=".zw" class="sr-only" @change="handleHomeImportChange" /> <input ref="homeImportInputRef" type="file" accept=".zw" class="sr-only" @change="handleHomeImportChange" />
<div class="flex min-h-full items-center justify-center px-4 py-8 lg:py-10"> <div class="home-entry relative flex min-h-full items-center justify-center px-4 py-8 lg:py-10">
<div class="w-full max-w-[1240px]"> <div class="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_80%_60%_at_50%_-10%,rgba(59,130,246,0.06),transparent_70%)]" />
<div class="grid items-stretch gap-6 lg:grid-cols-[320px_1fr]"> <div class="relative w-full max-w-[1240px]">
<div class="grid items-stretch gap-6 lg:grid-cols-[300px_1fr]">
<div <div
class="relative overflow-hidden rounded-2xl bg-[linear-gradient(145deg,#041c5f_0%,#0b2e86_50%,#354cc7_100%)] p-8 text-white shadow-[0_24px_60px_rgba(11,46,134,0.45)]" class="home-hero relative overflow-hidden rounded-2xl bg-[linear-gradient(145deg,#5c0a0a_0%,#991b1b_50%,#dc2626_100%)] p-7 text-white shadow-[0_24px_60px_rgba(153,27,27,0.45)]"
> >
<div class="pointer-events-none absolute -right-20 -top-16 h-56 w-56 rounded-full bg-white/12 blur-2xl" /> <div class="pointer-events-none absolute -right-20 -top-16 h-56 w-56 rounded-full bg-white/12 blur-2xl" />
<div class="pointer-events-none absolute -left-10 -bottom-10 h-40 w-40 rounded-full bg-white/8 blur-3xl" />
<div <div
class="pointer-events-none absolute inset-0 bg-[repeating-linear-gradient(140deg,rgba(255,255,255,0.06)_0,rgba(255,255,255,0.06)_1px,transparent_1px,transparent_20px)]" class="pointer-events-none absolute inset-0 bg-[repeating-linear-gradient(140deg,rgba(255,255,255,0.06)_0,rgba(255,255,255,0.06)_1px,transparent_1px,transparent_20px)]"
/> />
<div class="relative inline-flex h-12 w-12 items-center justify-center rounded-xl bg-white/15 ring-1 ring-white/35"> <div class="relative inline-flex h-11 w-11 items-center justify-center rounded-xl bg-white/15 ring-1 ring-white/35">
<Calculator class="h-6 w-6" /> <Calculator class="h-5 w-5" />
</div> </div>
<h2 class="relative mt-10 text-3xl font-semibold leading-tight tracking-tight">智能预算一键生成-></h2> <h2 class="relative mt-8 text-2xl font-semibold leading-tight tracking-tight lg:text-3xl">智能预算一键生成</h2>
<p class="relative mt-3 text-base text-blue-100">助力规范高效落地</p> <p class="relative mt-2 text-sm text-red-200/90">助力规范高效落地</p>
<div class="relative mt-6 h-px bg-gradient-to-r from-white/20 via-white/10 to-transparent" />
<p class="relative mt-4 text-xs leading-5 text-red-200/60">交通建设项目工程造价咨询服务费计算</p>
</div> </div>
<div class="flex flex-col justify-center"> <div class="flex flex-col">
<div class="text-center"> <div class="home-title text-center">
<h1 class="text-3xl font-semibold tracking-tight text-slate-900">计算入口</h1> <h1 class="text-2xl font-semibold tracking-tight text-slate-900 lg:text-3xl">计算入口</h1>
<p class="mt-2 text-sm text-slate-600">支持三种计算方式项目计算 · 单项速算 · 导入数据</p> <p class="mt-1.5 text-sm text-slate-500">项目计算 · 单项速算 · 导入数据</p>
</div> </div>
<div class="mt-6 grid gap-4 md:grid-cols-2 xl:grid-cols-3"> <div class="mt-5 grid flex-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
<Card <Card
role="button" role="button"
tabindex="0" tabindex="0"
class="group flex cursor-pointer flex-col rounded-2xl border border-slate-200/80 bg-white/95 p-5 shadow-[0_10px_30px_rgba(15,23,42,0.08)] backdrop-blur-sm transition-all duration-200 hover:-translate-y-1 hover:shadow-[0_20px_45px_rgba(15,23,42,0.16)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-200" class="home-card group flex cursor-pointer flex-col justify-between rounded-xl border border-slate-200/80 bg-white/95 px-5 py-5 shadow-[0_4px_20px_rgba(15,23,42,0.06)] backdrop-blur-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_12px_32px_rgba(15,23,42,0.12)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-200"
@click="openProjectCalc" @click="openProjectCalc"
@keydown.enter.prevent="openProjectCalc" @keydown.enter.prevent="openProjectCalc"
@keydown.space.prevent="openProjectCalc" @keydown.space.prevent="openProjectCalc"
> >
<CardHeader class="space-y-3 p-0"> <CardHeader class="p-0">
<div>
<CardTitle class="text-2xl font-semibold text-slate-900">项目预算</CardTitle>
<CardDescription class="mt-2 text-sm leading-6 text-slate-500">
适用多合同段项目级整体计算
<br>
操作支持当前项目及合同段卡片和流程线可导出/导入完整项目数据
</CardDescription>
</div>
</CardHeader>
<CardContent class="mt-auto flex flex-col items-center p-0 pt-8">
<div <div
class="mt-4 inline-flex h-16 w-16 items-center justify-center rounded-full border border-blue-100 bg-[radial-gradient(circle_at_30%_30%,#ffffff,#eef4ff)] shadow-[0_10px_24px_rgba(27,76,176,0.18)]" class="inline-flex h-11 w-11 items-center justify-center rounded-xl border border-blue-100 bg-blue-50/80 shadow-sm"
> >
<img <img
v-if="projectIconAvailable" v-if="projectIconAvailable"
:src="'/image_0.png'" :src="'/image_0.png'"
alt="项目预算" alt="项目预算"
class="h-8 w-8 object-contain" class="h-5 w-5 object-contain"
@error="projectIconAvailable = false" @error="projectIconAvailable = false"
> >
<FolderKanban v-else class="h-7 w-7 text-blue-700" /> <FolderKanban v-else class="h-5 w-5 text-blue-600" />
</div> </div>
</CardContent> <CardTitle class="mt-4 text-base font-semibold text-slate-900">项目预算</CardTitle>
<CardDescription class="mt-1.5 text-xs leading-5 text-slate-500">
适用于多合同段项目级整体计算支持导出/导入完整项目数据
</CardDescription>
</CardHeader>
<div class="mt-4 flex items-center text-xs font-medium text-slate-400 transition-colors group-hover:text-slate-600">
<span>进入计算</span>
<svg class="ml-1 h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</div>
</Card> </Card>
<Card <Card
role="button" role="button"
tabindex="0" tabindex="0"
class="group flex cursor-pointer flex-col rounded-2xl border border-slate-200/80 bg-white/95 p-5 shadow-[0_10px_30px_rgba(15,23,42,0.08)] backdrop-blur-sm transition-all duration-200 hover:-translate-y-1 hover:shadow-[0_20px_45px_rgba(15,23,42,0.16)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-200" class="home-card group flex cursor-pointer flex-col justify-between rounded-xl border border-slate-200/80 bg-white/95 px-5 py-5 shadow-[0_4px_20px_rgba(15,23,42,0.06)] backdrop-blur-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_12px_32px_rgba(15,23,42,0.12)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-200"
@click="openQuickCalcDialog" @click="openQuickCalcDialog"
@keydown.enter.prevent="openQuickCalcDialog" @keydown.enter.prevent="openQuickCalcDialog"
@keydown.space.prevent="openQuickCalcDialog" @keydown.space.prevent="openQuickCalcDialog"
> >
<CardHeader class="space-y-3 p-0"> <CardHeader class="p-0">
<div>
<CardTitle class="text-2xl font-semibold text-slate-900">单项速算</CardTitle>
<CardDescription class="mt-2 text-sm leading-6 text-slate-500">
适用单合同段预算单项试算
<br>
操作选择行业与咨询类型输入基数秒出结果
</CardDescription>
</div>
</CardHeader>
<CardContent class="mt-auto flex flex-col items-center p-0 pt-8">
<div <div
class="mt-4 inline-flex h-16 w-16 items-center justify-center rounded-full border border-blue-100 bg-[radial-gradient(circle_at_30%_30%,#ffffff,#eef4ff)] shadow-[0_10px_24px_rgba(27,76,176,0.18)]" class="inline-flex h-11 w-11 items-center justify-center rounded-xl border border-amber-100 bg-amber-50/80 shadow-sm"
> >
<img <img
v-if="quickIconAvailable" v-if="quickIconAvailable"
:src="'/image_1.png'" :src="'/image_1.png'"
alt="单项速算" alt="单项速算"
class="h-8 w-8 object-contain" class="h-5 w-5 object-contain"
@error="quickIconAvailable = false" @error="quickIconAvailable = false"
> >
<Zap v-else class="h-7 w-7 text-amber-500" /> <Zap v-else class="h-5 w-5 text-amber-500" />
</div> </div>
</CardContent> <CardTitle class="mt-4 text-base font-semibold text-slate-900">单项速算</CardTitle>
<CardDescription class="mt-1.5 text-xs leading-5 text-slate-500">
单合同段预算单项试算选择行业与咨询类型输入基数秒出结果
</CardDescription>
</CardHeader>
<div class="mt-4 flex items-center text-xs font-medium text-slate-400 transition-colors group-hover:text-slate-600">
<span>进入计算</span>
<svg class="ml-1 h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</div>
</Card> </Card>
<Card <Card
role="button" role="button"
tabindex="0" tabindex="0"
class="group flex cursor-pointer flex-col rounded-2xl border border-slate-200/80 bg-white/95 p-5 shadow-[0_10px_30px_rgba(15,23,42,0.08)] backdrop-blur-sm transition-all duration-200 hover:-translate-y-1 hover:shadow-[0_20px_45px_rgba(15,23,42,0.16)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-200" class="home-card group flex cursor-pointer flex-col justify-between rounded-xl border border-slate-200/80 bg-white/95 px-5 py-5 shadow-[0_4px_20px_rgba(15,23,42,0.06)] backdrop-blur-sm transition-all duration-200 hover:-translate-y-0.5 hover:shadow-[0_12px_32px_rgba(15,23,42,0.12)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-200"
@click="openHomeImport" @click="openHomeImport"
@keydown.enter.prevent="openHomeImport" @keydown.enter.prevent="openHomeImport"
@keydown.space.prevent="openHomeImport" @keydown.space.prevent="openHomeImport"
> >
<CardHeader class="space-y-3 p-0"> <CardHeader class="p-0">
<div>
<CardTitle class="text-2xl font-semibold text-slate-900">导入数据</CardTitle>
<CardDescription class="mt-2 text-sm leading-6 text-slate-500">
适用续未完成工作跨设备恢复数据
<br>
操作导入".zw"数据包快速恢复项目计算状态
</CardDescription>
</div>
</CardHeader>
<CardContent class="mt-auto flex flex-col items-center p-0 pt-8">
<div <div
class="mt-4 inline-flex h-16 w-16 items-center justify-center rounded-full border border-blue-100 bg-[radial-gradient(circle_at_30%_30%,#ffffff,#eef4ff)] shadow-[0_10px_24px_rgba(27,76,176,0.18)]" class="inline-flex h-11 w-11 items-center justify-center rounded-xl border border-cyan-100 bg-cyan-50/80 shadow-sm"
> >
<img <img
v-if="importIconAvailable" v-if="importIconAvailable"
:src="'/image_2.png'" :src="'/image_2.png'"
alt="导入数据" alt="导入数据"
class="h-8 w-8 object-contain" class="h-5 w-5 object-contain"
@error="importIconAvailable = false" @error="importIconAvailable = false"
> >
<Download v-else class="h-7 w-7 text-cyan-600" /> <Download v-else class="h-5 w-5 text-cyan-600" />
</div> </div>
</CardContent> <CardTitle class="mt-4 text-base font-semibold text-slate-900">导入数据</CardTitle>
<CardDescription class="mt-1.5 text-xs leading-5 text-slate-500">
导入".zw"数据包快速恢复项目计算状态续未完成工作
</CardDescription>
</CardHeader>
<div class="mt-4 flex items-center text-xs font-medium text-slate-400 transition-colors group-hover:text-slate-600">
<span>选择文件</span>
<svg class="ml-1 h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>
</div>
</Card> </Card>
</div> </div>
</div> </div>
@ -504,3 +502,24 @@ onMounted(() => {
</div> </div>
</div> </div>
</template> </template>
<style scoped>
.home-hero {
animation: hero-in 0.5s cubic-bezier(0.22, 1, 0.36, 1) both;
}
.home-title {
animation: fade-up 0.45s cubic-bezier(0.22, 1, 0.36, 1) 0.15s both;
}
.home-card:nth-child(1) { animation: fade-up 0.45s cubic-bezier(0.22, 1, 0.36, 1) 0.25s both; }
.home-card:nth-child(2) { animation: fade-up 0.45s cubic-bezier(0.22, 1, 0.36, 1) 0.35s both; }
.home-card:nth-child(3) { animation: fade-up 0.45s cubic-bezier(0.22, 1, 0.36, 1) 0.45s both; }
@keyframes hero-in {
from { opacity: 0; transform: translateX(-20px) scale(0.97); }
to { opacity: 1; transform: translateX(0) scale(1); }
}
@keyframes fade-up {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
</style>

View File

@ -1687,7 +1687,6 @@ const handleReset = async () => {
} finally { } finally {
tabStore.resetTabs() tabStore.resetTabs()
await tabStore.$persistNow?.() await tabStore.$persistNow?.()
window.location.reload()
} }
} }
@ -1900,7 +1899,7 @@ watch(
<div class="flex-1 overflow-auto relative"> <div class="flex-1 overflow-auto relative">
<div v-for="tab in tabStore.tabs" :key="tab.id" :ref="el => setTabPanelRef(tab.id, el)" <div v-for="tab in tabStore.tabs" :key="tab.id" :ref="el => setTabPanelRef(tab.id, el)"
v-show="tabStore.activeTabId === tab.id" class="h-full w-full p-4 animate-in fade-in duration-300"> v-show="tabStore.activeTabId === tab.id" class="h-full w-full animate-in fade-in duration-300">
<component :is="componentMap[tab.componentName]" v-bind="tab.props || {}" /> <component :is="componentMap[tab.componentName]" v-bind="tab.props || {}" />
</div> </div>
</div> </div>

View File

@ -199,7 +199,7 @@ useMotionValueEvent(
<template> <template>
<TooltipProvider> <TooltipProvider>
<div class="flex h-full w-full bg-background"> <div class="flex h-full w-full bg-background">
<div class="w-12/100 border-r p-2 flex flex-col gap-8 relative"> <div class="w-[200px] shrink-0 border-r px-4 py-3 flex flex-col gap-6 relative">
<div v-if="props.title || props.subtitle || props.metaText" class="space-y-1"> <div v-if="props.title || props.subtitle || props.metaText" class="space-y-1">
<TooltipRoot> <TooltipRoot>
<TooltipTrigger as-child> <TooltipTrigger as-child>
@ -228,23 +228,23 @@ useMotionValueEvent(
</div> </div>
</div> </div>
<div class="flex flex-col gap-10 relative"> <div class="flex flex-col gap-5 relative">
<div class="absolute left-[11px] top-2 bottom-2 w-[2px] bg-muted"></div> <div class="absolute left-[9px] top-3 bottom-3 w-[1.5px] bg-border/60"></div>
<div v-for="item in props.categories" :key="item.key" <div v-for="item in props.categories" :key="item.key"
class="relative flex items-center gap-4 cursor-pointer group" @click="switchCategory(item.key)"> class="relative flex items-center gap-3 cursor-pointer group" @click="switchCategory(item.key)">
<div :class="[ <div :class="[
'z-10 w-6 h-6 rounded-full border-2 flex items-center justify-center transition-all', 'z-10 w-5 h-5 rounded-full border-2 flex items-center justify-center transition-all duration-200',
activeCategory === item.key activeCategory === item.key
? 'bg-primary border-primary scale-110' ? 'bg-primary border-primary shadow-[0_0_0_3px_rgba(var(--primary),0.15)]'
: 'bg-background border-muted-foreground' : 'bg-background border-muted-foreground/40 group-hover:border-muted-foreground/70'
]"> ]">
<div v-if="activeCategory === item.key" class="w-2 h-2 bg-background rounded-full"></div> <div v-if="activeCategory === item.key" class="w-1.5 h-1.5 bg-background rounded-full"></div>
</div> </div>
<span :class="[ <span :class="[
'text-sm transition-colors', 'text-[13px] leading-5 transition-colors duration-200',
activeCategory === item.key activeCategory === item.key
? 'font-bold text-primary' ? 'font-semibold text-primary'
: 'text-muted-foreground group-hover:text-foreground' : 'text-muted-foreground group-hover:text-foreground'
]"> ]">
{{ item.label }} {{ item.label }}
@ -255,7 +255,7 @@ useMotionValueEvent(
<DialogRoot v-model:open="sheetOpen"> <DialogRoot v-model:open="sheetOpen">
<DialogTrigger as-child> <DialogTrigger as-child>
<button type="button" <button type="button"
class="cursor-pointer absolute left-4 right-4 bottom-4 flex flex-col items-center gap-1.5 rounded-lg border bg-muted/35 px-3 py-2 text-center text-[12px] leading-5 text-foreground/85 shadow-sm transition-colors hover:bg-muted/55 hover:text-foreground"> class="cursor-pointer absolute left-3 right-3 bottom-3 flex flex-col items-center gap-1.5 rounded-lg border bg-muted/35 px-3 py-2 text-center text-[11px] leading-4 text-foreground/85 shadow-sm transition-colors hover:bg-muted/55 hover:text-foreground">
<img src="/favicon.ico" alt="众为咨询" class="h-5 w-5 shrink-0 rounded-sm" /> <img src="/favicon.ico" alt="众为咨询" class="h-5 w-5 shrink-0 rounded-sm" />
<span>本网站由众为工程咨询有限公司提供免费技术支持</span> <span>本网站由众为工程咨询有限公司提供免费技术支持</span>
</button> </button>
@ -337,9 +337,9 @@ useMotionValueEvent(
</div> </div>
<div class="w-88/100 min-h-0 h-full flex flex-col"> <div class="flex-1 min-w-0 min-h-0 h-full flex flex-col">
<ScrollArea class="h-full w-full min-h-0 rightMain"> <ScrollArea class="h-full w-full min-h-0 rightMain">
<div class="p-3 h-full min-h-0 flex flex-col"> <div class="p-4 h-full min-h-0 flex flex-col">
<keep-alive> <keep-alive>
<component :is="activeComponent" /> <component :is="activeComponent" />
</keep-alive> </keep-alive>