526 lines
22 KiB
Vue
526 lines
22 KiB
Vue
<script setup lang="ts">
|
||
import { onMounted, ref } from 'vue'
|
||
import { Card, 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 homeImportInputRef = ref<HTMLInputElement | null>(null)
|
||
const projectIconAvailable = ref(false)
|
||
const quickIconAvailable = ref(false)
|
||
const importIconAvailable = 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: 'ProjectCalcView'
|
||
})
|
||
tabStore.hasCompletedSetup = true
|
||
}
|
||
|
||
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 () => {
|
||
|
||
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: 'QuickCalcWorkbenchView',
|
||
props: {
|
||
contractId: QUICK_CONTRACT_ID,
|
||
contractName,
|
||
projectInfoKey: QUICK_PROJECT_INFO_KEY,
|
||
projectScaleKey: null,
|
||
projectConsultCategoryFactorKey: QUICK_CONSULT_CATEGORY_FACTOR_KEY,
|
||
projectMajorFactorKey: QUICK_MAJOR_FACTOR_KEY
|
||
}
|
||
})
|
||
tabStore.hasCompletedSetup = true
|
||
} 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 = ''
|
||
}
|
||
|
||
const openHomeImport = () => {
|
||
homeImportInputRef.value?.click()
|
||
}
|
||
|
||
onMounted(() => {
|
||
void loadProjectDefaults()
|
||
void loadQuickDefaults()
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<input ref="homeImportInputRef" type="file" accept=".zw" class="sr-only" @change="handleHomeImportChange" />
|
||
<div class="home-entry relative flex min-h-full items-center justify-center px-4 py-8 lg:py-10">
|
||
<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="relative w-full max-w-[1240px]">
|
||
<div class="grid items-stretch gap-6 lg:grid-cols-[300px_1fr]">
|
||
<div
|
||
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 -left-10 -bottom-10 h-40 w-40 rounded-full bg-white/8 blur-3xl" />
|
||
<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)]"
|
||
/>
|
||
<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-5 w-5" />
|
||
</div>
|
||
<h2 class="relative mt-8 text-2xl font-semibold leading-tight tracking-tight lg:text-3xl">智能预算一键生成</h2>
|
||
<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 class="flex flex-col">
|
||
<div class="home-title text-center">
|
||
<h1 class="text-2xl font-semibold tracking-tight text-slate-900 lg:text-3xl">计算入口</h1>
|
||
<p class="mt-1.5 text-sm text-slate-500">项目计算 · 单项速算 · 导入数据</p>
|
||
</div>
|
||
<div class="mt-5 grid flex-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||
<Card
|
||
role="button"
|
||
tabindex="0"
|
||
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"
|
||
@keydown.enter.prevent="openProjectCalc"
|
||
@keydown.space.prevent="openProjectCalc"
|
||
>
|
||
<CardHeader class="p-0">
|
||
<div
|
||
class="inline-flex h-11 w-11 items-center justify-center rounded-xl border border-blue-100 bg-blue-50/80 shadow-sm"
|
||
>
|
||
<img
|
||
v-if="projectIconAvailable"
|
||
:src="'/image_0.png'"
|
||
alt="项目预算"
|
||
class="h-5 w-5 object-contain"
|
||
@error="projectIconAvailable = false"
|
||
>
|
||
<FolderKanban v-else class="h-5 w-5 text-blue-600" />
|
||
</div>
|
||
<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
|
||
role="button"
|
||
tabindex="0"
|
||
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"
|
||
@keydown.enter.prevent="openQuickCalcDialog"
|
||
@keydown.space.prevent="openQuickCalcDialog"
|
||
>
|
||
<CardHeader class="p-0">
|
||
<div
|
||
class="inline-flex h-11 w-11 items-center justify-center rounded-xl border border-amber-100 bg-amber-50/80 shadow-sm"
|
||
>
|
||
<img
|
||
v-if="quickIconAvailable"
|
||
:src="'/image_1.png'"
|
||
alt="单项速算"
|
||
class="h-5 w-5 object-contain"
|
||
@error="quickIconAvailable = false"
|
||
>
|
||
<Zap v-else class="h-5 w-5 text-amber-500" />
|
||
</div>
|
||
<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
|
||
role="button"
|
||
tabindex="0"
|
||
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"
|
||
@keydown.enter.prevent="openHomeImport"
|
||
@keydown.space.prevent="openHomeImport"
|
||
>
|
||
<CardHeader class="p-0">
|
||
<div
|
||
class="inline-flex h-11 w-11 items-center justify-center rounded-xl border border-cyan-100 bg-cyan-50/80 shadow-sm"
|
||
>
|
||
<img
|
||
v-if="importIconAvailable"
|
||
:src="'/image_2.png'"
|
||
alt="导入数据"
|
||
class="h-5 w-5 object-contain"
|
||
@error="importIconAvailable = false"
|
||
>
|
||
<Download v-else class="h-5 w-5 text-cyan-600" />
|
||
</div>
|
||
<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>
|
||
</div>
|
||
</div>
|
||
</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"
|
||
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 placeholder:text-slate-400 hover:border-border focus-visible:ring-2 focus-visible:ring-slate-300"
|
||
placeholder="请输入合同名称"
|
||
>
|
||
</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 || !quickContractName" @click="confirmQuickCalc">
|
||
{{ quickSubmitting ? '进入中...' : '进入计算' }}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</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>
|