1
This commit is contained in:
parent
8cc542110b
commit
50e71f786d
Binary file not shown.
BIN
public/related-files.png
Normal file
BIN
public/related-files.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 856 B |
@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
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'
|
||||||
@ -82,12 +81,13 @@ const PROJECT_INFO_KEY = 'xm-base-info-v1'
|
|||||||
const PROJECT_CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
|
const PROJECT_CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
|
||||||
const PROJECT_MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
const PROJECT_MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
||||||
const PROJECT_SCALE_KEY = 'xm-info-v3'
|
const PROJECT_SCALE_KEY = 'xm-info-v3'
|
||||||
|
const FILE_LEDGER_URL = 'https://www.lianzhong.com.cn/file?fileNo=24'
|
||||||
const getActiveProjectId = () => readCurrentProjectId()
|
const getActiveProjectId = () => readCurrentProjectId()
|
||||||
|
|
||||||
const tabStore = useTabStore()
|
const tabStore = useTabStore()
|
||||||
const kvStore = useKvStore()
|
const kvStore = useKvStore()
|
||||||
const uiPrefsStore = useUiPrefsStore()
|
const uiPrefsStore = useUiPrefsStore()
|
||||||
const { t, locale } = useI18n()
|
const { t, tm, locale } = useI18n()
|
||||||
const projectDialogOpen = ref(false)
|
const projectDialogOpen = ref(false)
|
||||||
const projectIndustry = ref(String(industryTypeList[0]?.id || ''))
|
const projectIndustry = ref(String(industryTypeList[0]?.id || ''))
|
||||||
const projectSubmitting = ref(false)
|
const projectSubmitting = ref(false)
|
||||||
@ -105,6 +105,8 @@ const existingProjectLoading = ref(false)
|
|||||||
const hasExistingProjects = ref(false)
|
const hasExistingProjects = ref(false)
|
||||||
const openedProjectIds = ref<string[]>([])
|
const openedProjectIds = ref<string[]>([])
|
||||||
let existingProjectPollTimer: ReturnType<typeof setInterval> | null = null
|
let existingProjectPollTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
const heroTitleIndex = ref(0)
|
||||||
|
const heroDescIndex = ref(0)
|
||||||
const projectIndustryLabel = computed(() => {
|
const projectIndustryLabel = computed(() => {
|
||||||
const target = String(projectIndustry.value || '').trim()
|
const target = String(projectIndustry.value || '').trim()
|
||||||
if (!target) return ''
|
if (!target) return ''
|
||||||
@ -186,6 +188,10 @@ const openProjectCalc = async () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const openRelatedFiles = () => {
|
||||||
|
window.open(FILE_LEDGER_URL, '_blank', 'noopener')
|
||||||
|
}
|
||||||
|
|
||||||
const redirectToDisclaimerPage = () => {
|
const redirectToDisclaimerPage = () => {
|
||||||
const returnUrl = window.location.href
|
const returnUrl = window.location.href
|
||||||
window.location.href = buildDisclaimerUrl(returnUrl)
|
window.location.href = buildDisclaimerUrl(returnUrl)
|
||||||
@ -457,12 +463,80 @@ const openDisclaimerPage = () => {
|
|||||||
window.open(href, '_blank', 'noopener')
|
window.open(href, '_blank', 'noopener')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolveLocalizedStringArray = (value: unknown) => {
|
||||||
|
if (!Array.isArray(value)) return []
|
||||||
|
return value.filter((item): item is string => typeof item === 'string' && item.trim().length > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const heroTitleOptions = computed(() =>
|
||||||
|
resolveLocalizedStringArray(tm('home.cards.heroTitles'))
|
||||||
|
)
|
||||||
|
const heroDescOptions = computed(() =>
|
||||||
|
resolveLocalizedStringArray(tm('home.cards.heroDescs'))
|
||||||
|
)
|
||||||
|
const heroTitleText = computed(() => heroTitleOptions.value[heroTitleIndex.value] || '')
|
||||||
|
const heroDescText = computed(() => heroDescOptions.value[heroDescIndex.value] || '')
|
||||||
|
|
||||||
|
const pickRandomIndex = (length: number) => {
|
||||||
|
if (length <= 1) return 0
|
||||||
|
return Math.floor(Math.random() * length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshHeroCopy = () => {
|
||||||
|
heroTitleIndex.value = pickRandomIndex(heroTitleOptions.value.length)
|
||||||
|
heroDescIndex.value = pickRandomIndex(heroDescOptions.value.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const homeActionCards = [
|
||||||
|
{
|
||||||
|
key: 'projectBudget',
|
||||||
|
desc: 'projectBudgetDesc',
|
||||||
|
action: 'enter',
|
||||||
|
icon: 'project',
|
||||||
|
iconWrapClass: 'border-blue-100 bg-blue-50/80 text-blue-600',
|
||||||
|
iconClass: 'h-5 w-5',
|
||||||
|
clickFunc: openProjectCalc,
|
||||||
|
showExistingAction: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'quickCalc',
|
||||||
|
desc: 'quickCalcDesc',
|
||||||
|
action: 'enter',
|
||||||
|
icon: 'quick',
|
||||||
|
iconWrapClass: 'border-amber-100 bg-amber-50/80 text-amber-600',
|
||||||
|
iconClass: 'h-10 w-10',
|
||||||
|
clickFunc: openQuickCalc,
|
||||||
|
showExistingAction: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'importData',
|
||||||
|
desc: 'importDataDesc',
|
||||||
|
action: 'pickFile',
|
||||||
|
icon: 'import',
|
||||||
|
iconWrapClass: 'border-emerald-100 bg-emerald-50/80 text-emerald-600',
|
||||||
|
iconClass: 'h-5 w-5',
|
||||||
|
clickFunc: openHomeImport,
|
||||||
|
showExistingAction: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'file',
|
||||||
|
desc: 'fileDataDesc',
|
||||||
|
action: 'openFileSystem',
|
||||||
|
icon: 'files',
|
||||||
|
iconWrapClass: 'border-emerald-100 bg-emerald-50/80 text-emerald-600',
|
||||||
|
iconClass: 'h-5 w-5',
|
||||||
|
clickFunc: openRelatedFiles,
|
||||||
|
showExistingAction: false
|
||||||
|
}
|
||||||
|
] as const
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
syncDisclaimerRequirement()
|
syncDisclaimerRequirement()
|
||||||
void refreshExistingProjects()
|
void refreshExistingProjects()
|
||||||
void loadProjectDefaults()
|
void loadProjectDefaults()
|
||||||
void loadQuickDefaults()
|
void loadQuickDefaults()
|
||||||
void replayPendingDisclaimerAction()
|
void replayPendingDisclaimerAction()
|
||||||
|
refreshHeroCopy()
|
||||||
window.addEventListener('focus', handleHomeWindowFocus)
|
window.addEventListener('focus', handleHomeWindowFocus)
|
||||||
document.addEventListener('visibilitychange', handleHomeVisibilityChange)
|
document.addEventListener('visibilitychange', handleHomeVisibilityChange)
|
||||||
try {
|
try {
|
||||||
@ -490,11 +564,18 @@ onBeforeUnmount(() => {
|
|||||||
window.removeEventListener('focus', handleHomeWindowFocus)
|
window.removeEventListener('focus', handleHomeWindowFocus)
|
||||||
document.removeEventListener('visibilitychange', handleHomeVisibilityChange)
|
document.removeEventListener('visibilitychange', handleHomeVisibilityChange)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => locale.value,
|
||||||
|
() => {
|
||||||
|
refreshHeroCopy()
|
||||||
|
}
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<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="home-entry relative flex min-h-full items-center justify-center px-4 py-8 lg:py-10">
|
<div class="home-entry relative flex min-h-screen 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="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="relative w-full max-w-[1240px]">
|
||||||
<div class="absolute right-0 top-0 z-10">
|
<div class="absolute right-0 top-0 z-10">
|
||||||
@ -508,88 +589,58 @@ onBeforeUnmount(() => {
|
|||||||
<span>{{ localeBadge }}</span>
|
<span>{{ localeBadge }}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="home-title text-center">
|
<div class="home-title text-center" :style="{ maxWidth: '800px' }">
|
||||||
<h1 class="text-2xl font-semibold tracking-tight text-slate-900 lg:text-3xl">{{ t('home.title') }}</h1>
|
<h1
|
||||||
|
class="text-2xl tracking-tight text-slate-900 lg:text-3xl"
|
||||||
|
:style="{ whiteSpace: 'pre-line', fontWeight: '200', fontFamily: 'HarmonyOS_Sans_SC' }"
|
||||||
|
>
|
||||||
|
{{ t('home.title') }}
|
||||||
|
</h1>
|
||||||
<p class="mt-1.5 text-sm text-slate-500">{{ t('home.subtitle') }}</p>
|
<p class="mt-1.5 text-sm text-slate-500">{{ t('home.subtitle') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5 grid items-stretch gap-4 md:grid-cols-2 xl:grid-cols-4">
|
<div class="mt-5 grid items-stretch gap-4 md:grid-cols-2 xl:grid-cols-5">
|
||||||
<div
|
<div
|
||||||
class="home-hero home-card-base home-entry-item home-entry-item--1 relative overflow-hidden rounded-2xl bg-[#dc2626] p-7 text-white shadow-[0_24px_60px_rgba(153,27,27,0.35)]"
|
class="home-hero home-card-base home-entry-item home-entry-item--1 relative overflow-hidden rounded-2xl bg-[#dc2626] p-7 text-white shadow-[0_24px_60px_rgba(153,27,27,0.35)]"
|
||||||
>
|
>
|
||||||
<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 class="pointer-events-none absolute -left-10 -bottom-10 h-40 w-40 rounded-full bg-white/8 blur-3xl" />
|
||||||
<div class="home-hero-meteor home-hero-meteor--1" />
|
<div v-for="index in 10" :key="index" :class="`home-hero-meteor home-hero-meteor--${index}`" />
|
||||||
<div class="home-hero-meteor home-hero-meteor--2" />
|
|
||||||
<div class="home-hero-meteor home-hero-meteor--3" />
|
|
||||||
<div class="home-hero-meteor home-hero-meteor--4" />
|
|
||||||
<div class="home-hero-meteor home-hero-meteor--5" />
|
|
||||||
<div class="home-hero-meteor home-hero-meteor--6" />
|
|
||||||
<div class="home-hero-meteor home-hero-meteor--7" />
|
|
||||||
<div class="home-hero-meteor home-hero-meteor--8" />
|
|
||||||
<div class="home-hero-meteor home-hero-meteor--9" />
|
|
||||||
<div class="home-hero-meteor home-hero-meteor--10" />
|
|
||||||
<div class="relative inline-flex h-11 w-11 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-5 w-5" />
|
<Calculator class="h-5 w-5" />
|
||||||
</div>
|
</div>
|
||||||
<h2 class="relative mt-8 text-2xl font-semibold leading-tight tracking-tight lg:text-3xl">{{ t('home.cards.heroTitle') }}</h2>
|
<h2 class="relative mt-8 text-2xl font-semibold leading-tight tracking-tight lg:text-3xl" :style="{ whiteSpace: 'pre-line', fontSize: '20px' }">{{ heroTitleText }}</h2>
|
||||||
<p class="relative mt-2 text-sm text-red-200/90">{{ t('home.cards.heroSubTitle') }}</p>
|
<p class="relative mt-2 text-sm text-red-200/90">{{ t('home.cards.heroSubTitle') }}</p>
|
||||||
<div class="relative mt-6 h-px bg-white/20" />
|
<div class="relative mt-6 h-px bg-white/20" />
|
||||||
<p class="relative mt-4 whitespace-pre-line text-xs leading-5 text-red-200/60">{{ t('home.cards.heroDesc') }}</p>
|
<p class="relative mt-4 whitespace-pre-line text-xs leading-5 text-red-200/60">{{ heroDescText }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card
|
<article
|
||||||
|
v-for="(card, index) in homeActionCards"
|
||||||
|
:key="card.key"
|
||||||
role="button"
|
role="button"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="home-card home-card-base home-entry-item home-entry-item--2 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"
|
:class="[
|
||||||
@click="openProjectCalc"
|
'home-card home-card-base home-entry-item 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',
|
||||||
@keydown.enter.prevent="openProjectCalc"
|
`home-entry-item--${index + 2}`
|
||||||
@keydown.space.prevent="openProjectCalc"
|
]"
|
||||||
|
@click="card.clickFunc"
|
||||||
|
@keydown.enter.prevent="card.clickFunc"
|
||||||
|
@keydown.space.prevent="card.clickFunc"
|
||||||
>
|
>
|
||||||
<CardHeader class="p-0">
|
<div>
|
||||||
<div
|
<div
|
||||||
class="inline-flex h-11 w-11 items-center justify-center rounded-xl border border-blue-100 bg-blue-50/80 text-blue-600 shadow-sm transition-transform duration-200 group-hover:scale-105"
|
:class="[
|
||||||
|
'inline-flex h-11 w-11 items-center justify-center rounded-xl border shadow-sm transition-transform duration-200 group-hover:scale-105',
|
||||||
|
card.iconWrapClass
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
<svg viewBox="0 0 1024 1024" class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
<svg v-if="card.icon === 'project'" viewBox="0 0 1024 1024" :class="card.iconClass" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
<path
|
<path
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
d="M938.666667 874.666667c0 11.733333-9.6 21.333333-21.333334 21.333333H106.666667c-11.733333 0-21.333333-9.6-21.333334-21.333333s9.6-21.333333 21.333334-21.333334h42.666666V490.666667c0-11.733333 9.6-21.333333 21.333334-21.333334h170.666666c11.733333 0 21.333333 9.6 21.333334 21.333334v362.666666h42.666666V320c0-11.733333 9.6-21.333333 21.333334-21.333333h170.666666c11.733333 0 21.333333 9.6 21.333334 21.333333v533.333333h42.666666V149.333333c0-11.733333 9.6-21.333333 21.333334-21.333333h170.666666c11.733333 0 21.333333 9.6 21.333334 21.333333v704h42.666666c11.733333 0 21.333333 9.6 21.333334 21.333334z"
|
d="M938.666667 874.666667c0 11.733333-9.6 21.333333-21.333334 21.333333H106.666667c-11.733333 0-21.333333-9.6-21.333334-21.333333s9.6-21.333333 21.333334-21.333334h42.666666V490.666667c0-11.733333 9.6-21.333333 21.333334-21.333334h170.666666c11.733333 0 21.333333 9.6 21.333334 21.333334v362.666666h42.666666V320c0-11.733333 9.6-21.333333 21.333334-21.333333h170.666666c11.733333 0 21.333333 9.6 21.333334 21.333333v533.333333h42.666666V149.333333c0-11.733333 9.6-21.333333 21.333334-21.333333h170.666666c11.733333 0 21.333333 9.6 21.333334 21.333333v704h42.666666c11.733333 0 21.333333 9.6 21.333334 21.333334z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
<svg v-else-if="card.icon === 'quick'" viewBox="0 0 800 800" :class="card.iconClass" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
<CardTitle class="mt-4 text-base font-semibold text-slate-900">{{ t('home.cards.projectBudget') }}</CardTitle>
|
|
||||||
<CardDescription class="mt-1.5 text-xs leading-5 text-slate-500">
|
|
||||||
{{ t('home.cards.projectBudgetDesc') }}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<div class="mt-4 flex items-center justify-between gap-2">
|
|
||||||
<button
|
|
||||||
v-if="hasExistingProjects"
|
|
||||||
type="button"
|
|
||||||
class="cursor-pointer rounded-md border border-slate-200 px-2.5 py-1 text-xs font-medium text-slate-500 transition hover:border-slate-300 hover:bg-slate-50 hover:text-slate-700"
|
|
||||||
@click.stop="openExistingProjectDialog"
|
|
||||||
>
|
|
||||||
{{ t('home.cards.pickExisting') }}
|
|
||||||
</button>
|
|
||||||
<div class="flex items-center text-xs font-medium text-slate-400 transition-colors group-hover:text-slate-600">
|
|
||||||
<span>{{ t('home.cards.enter') }}</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>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
class="home-card home-card-base home-entry-item home-entry-item--3 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="openQuickCalc"
|
|
||||||
@keydown.enter.prevent="openQuickCalc"
|
|
||||||
@keydown.space.prevent="openQuickCalc"
|
|
||||||
>
|
|
||||||
<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 text-amber-600 shadow-sm transition-transform duration-200 group-hover:scale-105"
|
|
||||||
>
|
|
||||||
<svg viewBox="0 0 800 800" class="h-10 w-10" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
||||||
<path
|
<path
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
d="M5245 5891c-11-5-47-38-80-73-33-36-269-281-525-544-256-263-514-530-575-592-90-93-113-123-130-170-20-54-79-170-200-392-70-129-81-164-61-194 22-35 59-40 114-15 26 11 119 47 207 79 88 33 185 69 215 81 30 12 83 31 118 44 58 20 82 40 300 246 130 123 291 276 357 339 66 63 165 158 219 210 55 52 105 100 111 106 57 58 205 194 212 194 4 0 7-520 5-1155l-2-1155-553 0c-343 0-576-4-613-11-86-15-175-46-227-79-25-15-48-26-51-23-3 4-6 154-6 335l0 328-80 0-80 0 0-336 0-336-32 22c-44 29-106 56-176 77-50 15-130 17-647 22l-590 6 0 1165 0 1165 440 0c467 0 528-4 702-50 105-28 200-69 261-114 41-31 42-32 42-91l0-60 80 0 80 0 1 33c3 69 8 82 40 110 19 16 63 43 99 59 36 16 71 33 79 37 13 7 56 169 47 178-6 6-170-49-221-74-27-14-66-38-86-54l-35-28-32 25c-51 40-181 101-274 128-188 54-249 59-840 63l-548 4 0-1330 0-1331 619 0c669 0 715-3 826-54 63-29 138-94 155-136 12-30 13-30 91-30l78 0 17 35c20 44 70 87 137 122 114 58 98 56 807 62l655 6 3 1313c2 1296 2 1314 22 1339 34 43 21 153-29 252-35 67-147 173-224 211-70 34-179 50-222 31z m171-190c72-42 154-148 154-200 0-12-47-64-125-138-69-65-129-119-133-121-4-1-60 51-125 116l-117 118 121 127c136 144 141 146 225 98z m-341-468l110-114-65-62c-36-33-110-104-165-157-385-367-609-575-618-575-7 0-54 44-106 97l-94 97 335 343c184 189 366 376 403 416 38 39 73 71 79 71 6-1 61-53 121-116z m-905-994c0-7-193-79-211-79-5 0 14 46 42 101l51 102 59-59c32-32 59-61 59-65z"
|
d="M5245 5891c-11-5-47-38-80-73-33-36-269-281-525-544-256-263-514-530-575-592-90-93-113-123-130-170-20-54-79-170-200-392-70-129-81-164-61-194 22-35 59-40 114-15 26 11 119 47 207 79 88 33 185 69 215 81 30 12 83 31 118 44 58 20 82 40 300 246 130 123 291 276 357 339 66 63 165 158 219 210 55 52 105 100 111 106 57 58 205 194 212 194 4 0 7-520 5-1155l-2-1155-553 0c-343 0-576-4-613-11-86-15-175-46-227-79-25-15-48-26-51-23-3 4-6 154-6 335l0 328-80 0-80 0 0-336 0-336-32 22c-44 29-106 56-176 77-50 15-130 17-647 22l-590 6 0 1165 0 1165 440 0c467 0 528-4 702-50 105-28 200-69 261-114 41-31 42-32 42-91l0-60 80 0 80 0 1 33c3 69 8 82 40 110 19 16 63 43 99 59 36 16 71 33 79 37 13 7 56 169 47 178-6 6-170-49-221-74-27-14-66-38-86-54l-35-28-32 25c-51 40-181 101-274 128-188 54-249 59-840 63l-548 4 0-1330 0-1331 619 0c669 0 715-3 826-54 63-29 138-94 155-136 12-30 13-30 91-30l78 0 17 35c20 44 70 87 137 122 114 58 98 56 807 62l655 6 3 1313c2 1296 2 1314 22 1339 34 43 21 153-29 252-35 67-147 173-224 211-70 34-179 50-222 31z m171-190c72-42 154-148 154-200 0-12-47-64-125-138-69-65-129-119-133-121-4-1-60 51-125 116l-117 118 121 127c136 144 141 146 225 98z m-341-468l110-114-65-62c-36-33-110-104-165-157-385-367-609-575-618-575-7 0-54 44-106 97l-94 97 335 343c184 189 366 376 403 416 38 39 73 71 79 71 6-1 61-53 121-116z m-905-994c0-7-193-79-211-79-5 0 14 46 42 101l51 102 59-59c32-32 59-61 59-65z"
|
||||||
@ -606,47 +657,43 @@ onBeforeUnmount(() => {
|
|||||||
transform="translate(0,800) scale(0.1,-0.1)"
|
transform="translate(0,800) scale(0.1,-0.1)"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
<svg v-else-if="card.icon === 'import'" viewBox="0 0 1024 1024" :class="card.iconClass" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
<CardTitle class="mt-4 text-base font-semibold text-slate-900">{{ t('home.cards.quickCalc') }}</CardTitle>
|
|
||||||
<CardDescription class="mt-1.5 text-xs leading-5 text-slate-500">
|
|
||||||
{{ t('home.cards.quickCalcDesc') }}
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<div class="mt-4 flex items-center text-xs font-medium text-slate-400 transition-colors group-hover:text-slate-600">
|
|
||||||
<span>{{ t('home.cards.enter') }}</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 home-card-base home-entry-item home-entry-item--4 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-emerald-100 bg-emerald-50/80 text-emerald-600 shadow-sm transition-transform duration-200 group-hover:scale-105"
|
|
||||||
>
|
|
||||||
<svg viewBox="0 0 1024 1024" class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
||||||
<path
|
<path
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
d="M154.579478 1001.73913v-332.844521h89.043479V912.695652H912.695652V369.530435h-234.896695V111.304348H243.890087v349.184h-89.043478V22.26087h585.683478l261.431652 263.924869V1001.73913z m612.173913-721.252173h104.314435l-104.314435-105.293914z m-416.857043 411.469913l79.026087-79.026087H22.26087v-89.043479h406.661565L349.94087 444.861217l41.138087-41.22713 123.592347 123.592348 41.227131 41.182608-41.227131 41.138087-123.592347 123.592348z m123.013565-123.013566l0.489739-0.534261-0.489739-0.489739z"
|
d="M154.579478 1001.73913v-332.844521h89.043479V912.695652H912.695652V369.530435h-234.896695V111.304348H243.890087v349.184h-89.043478V22.26087h585.683478l261.431652 263.924869V1001.73913z m612.173913-721.252173h104.314435l-104.314435-105.293914z m-416.857043 411.469913l79.026087-79.026087H22.26087v-89.043479h406.661565L349.94087 444.861217l41.138087-41.22713 123.592347 123.592348 41.227131 41.182608-41.227131 41.138087-123.592347 123.592348z m123.013565-123.013566l0.489739-0.534261-0.489739-0.489739z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
<svg
|
||||||
|
v-else
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
:class="card.iconClass"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M271.146667 128h-85.333334a64 64 0 0 0-64 64v640a64 64 0 0 0 64 64h85.333334a64 64 0 0 0 64-64V192a64 64 0 0 0-64-64z m21.333333 704a21.333333 21.333333 0 0 1-21.333333 21.333333h-85.333334a21.333333 21.333333 0 0 1-21.333333-21.333333V320h128z m0-554.666667h-128V192a21.333333 21.333333 0 0 1 21.333333-21.333333h85.333334a21.333333 21.333333 0 0 1 21.333333 21.333333zM901.12 808.533333L789.333333 178.346667a64 64 0 0 0-74.026666-51.84l-84.053334 14.72a64 64 0 0 0-44.16 31.146666A64 64 0 0 0 527.146667 128h-85.333334a64 64 0 0 0-64 64v640a64 64 0 0 0 64 64h85.333334a64 64 0 0 0 64-64V279.253333l99.84 566.4a64 64 0 0 0 74.24 51.84l84.053333-14.72a64 64 0 0 0 51.84-74.24zM548.48 832a21.333333 21.333333 0 0 1-21.333333 21.333333h-85.333334a21.333333 21.333333 0 0 1-21.333333-21.333333V320h128z m0-554.666667h-128V192a21.333333 21.333333 0 0 1 21.333333-21.333333h85.333334a21.333333 21.333333 0 0 1 21.333333 21.333333z m73.386667-69.333333A21.333333 21.333333 0 0 1 640 183.253333l83.2-14.72a21.333333 21.333333 0 0 1 24.746667 17.28l14.933333 84.053334-126.08 21.333333z m219.946666 632.746667l-84.053333 14.72a21.333333 21.333333 0 0 1-24.746667-17.28L644.266667 334.08l125.866666-21.333333 88.96 504.106666a21.333333 21.333333 0 0 1-17.28 23.893334z"
|
||||||
|
fill="#e0db84"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<CardTitle class="mt-4 text-base font-semibold text-slate-900">{{ t('home.cards.importData') }}</CardTitle>
|
<h3 class="mt-4 text-base font-semibold text-slate-900">{{ t(`home.cards.${card.key}`) }}</h3>
|
||||||
<CardDescription class="mt-1.5 text-xs leading-5 text-slate-500">
|
<p class="mt-1.5 text-xs leading-5 text-slate-500">{{ t(`home.cards.${card.desc}`) }}</p>
|
||||||
{{ t('home.cards.importDataDesc') }}
|
</div>
|
||||||
</CardDescription>
|
<div class="mt-4 flex items-center justify-between gap-2">
|
||||||
</CardHeader>
|
<button
|
||||||
<div class="mt-4 flex items-center text-xs font-medium text-slate-400 transition-colors group-hover:text-slate-600">
|
v-if="card.showExistingAction && hasExistingProjects"
|
||||||
<span>{{ t('home.cards.pickFile') }}</span>
|
type="button"
|
||||||
|
class="cursor-pointer rounded-md border border-slate-200 px-2.5 py-1 text-xs font-medium text-slate-500 transition hover:border-slate-300 hover:bg-slate-50 hover:text-slate-700"
|
||||||
|
@click.stop="openExistingProjectDialog"
|
||||||
|
>
|
||||||
|
{{ t('home.cards.pickExisting') }}
|
||||||
|
</button>
|
||||||
|
<div class="flex items-center text-xs font-medium text-slate-400 transition-colors group-hover:text-slate-600">
|
||||||
|
<span>{{ t(`home.cards.${card.action}`) }}</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>
|
<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>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<div class="home-disclaimer mt-5 text-center">
|
<div class="home-disclaimer mt-5 text-center">
|
||||||
<button
|
<button
|
||||||
@ -830,6 +877,7 @@ onBeforeUnmount(() => {
|
|||||||
.home-entry-item--2 { animation-delay: 0.3s; }
|
.home-entry-item--2 { animation-delay: 0.3s; }
|
||||||
.home-entry-item--3 { animation-delay: 0.4s; }
|
.home-entry-item--3 { animation-delay: 0.4s; }
|
||||||
.home-entry-item--4 { animation-delay: 0.5s; }
|
.home-entry-item--4 { animation-delay: 0.5s; }
|
||||||
|
.home-entry-item--5 { animation-delay: 0.6s; }
|
||||||
.home-disclaimer {
|
.home-disclaimer {
|
||||||
animation: fade-up 0.45s cubic-bezier(0.22, 1, 0.36, 1) 0.6s both;
|
animation: fade-up 0.45s cubic-bezier(0.22, 1, 0.36, 1) 0.6s both;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,23 +19,40 @@ export const enUS = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
home: {
|
home: {
|
||||||
title: 'Calculation Entry',
|
title: 'Yue Gong Xue Biao Zi [2026] No. 5\nSpecification for Budget Preparation of Cost Consulting Services for Transportation Engineering',
|
||||||
subtitle: 'Project Budget · Quick Calc · Import Data',
|
subtitle: 'Project Budget · Quick Calc · Import Data · Related Files',
|
||||||
projectCalcTab: 'Project Calculation',
|
projectCalcTab: 'Project Calculation',
|
||||||
quickCalcTab: 'Quick Calculation',
|
quickCalcTab: 'Quick Calculation',
|
||||||
cards: {
|
cards: {
|
||||||
heroTitle: 'One-Click Smart Budget',
|
heroTitle: 'Zonghuiyi|Billing Made Simple',
|
||||||
heroSubTitle: 'Accelerate standards adoption',
|
heroTitles: [
|
||||||
heroDesc: 'Cost consulting fee calculator for transport construction projects',
|
'Zonghuiyi | Billing Made Simple',
|
||||||
|
'Zonghuiyi | No Late Nights for Billing',
|
||||||
|
'Zonghuiyi | Effortless Billing'
|
||||||
|
],
|
||||||
|
heroSubTitle: '',
|
||||||
|
heroDesc: 'Instant Pricing, Instant Results, Leave Time for Creation',
|
||||||
|
heroDescs: [
|
||||||
|
'Instant Pricing, Instant Results, Leave Time for Creation.',
|
||||||
|
'Smart Pricing, One Click to Results, Leave Time for Creation.',
|
||||||
|
'Smart Pricing, One Click to Generate, Leave Time for Creation.',
|
||||||
|
'Zonghuiyi. Simple, No Late Nights, Effortless.'
|
||||||
|
],
|
||||||
projectBudget: 'Project Budget',
|
projectBudget: 'Project Budget',
|
||||||
projectBudgetDesc: 'For full project-level calculation across multiple contracts with import/export support',
|
projectBudgetDesc: 'For full project-level calculation across multiple contracts with import/export support',
|
||||||
quickCalc: 'Quick Calc',
|
quickCalc: 'Quick Calc',
|
||||||
quickCalcDesc: 'Suitable for single service trial, select industry, consultation type, engineering specialty, input base number and get results in seconds',
|
quickCalcDesc: 'Suitable for single service trial, select industry, consultation type, engineering specialty, input base number and get results in seconds',
|
||||||
importData: 'Import Data',
|
importData: 'Import Data',
|
||||||
importDataDesc: 'Import a ".zw" package and create a new project automatically to restore the data without overriding existing projects',
|
importDataDesc: 'Import a ".zw" package and create a new project automatically to restore the data without overriding existing projects',
|
||||||
|
relatedFiles: 'Related Files',
|
||||||
|
relatedFilesDesc: 'View, print, and download the fee documents, related bidding documents, and contract templates used by this calculator',
|
||||||
|
viewFiles: 'View Files',
|
||||||
enter: 'Enter',
|
enter: 'Enter',
|
||||||
pickFile: 'Choose File',
|
pickFile: 'Choose File',
|
||||||
pickExisting: 'Choose Existing'
|
pickExisting: 'Choose Existing',
|
||||||
|
openFileSystem: 'Open File System',
|
||||||
|
file: 'File System',
|
||||||
|
fileDataDesc: 'The file system provides related fee documents, bidding documents, contract documents, service contents, and work requirements'
|
||||||
},
|
},
|
||||||
disclaimer: {
|
disclaimer: {
|
||||||
link: 'View Disclaimer',
|
link: 'View Disclaimer',
|
||||||
|
|||||||
@ -19,23 +19,40 @@ export const zhCN = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
home: {
|
home: {
|
||||||
title: '预算编制入口',
|
title: '粤公学标字〔2026〕5号\n交通运输工程造价咨询服务预算编制规范',
|
||||||
subtitle: '项目计算 · 单项速算 · 导入数据',
|
subtitle: '项目计算 · 单项速算 · 导入数据 · 相关文件',
|
||||||
projectCalcTab: '项目计算',
|
projectCalcTab: '项目计算',
|
||||||
quickCalcTab: '快速计算',
|
quickCalcTab: '快速计算',
|
||||||
cards: {
|
cards: {
|
||||||
heroTitle: '智能预算一键生成',
|
heroTitle: '众会易|算费真容易',
|
||||||
heroSubTitle: '助力《规范》高效落地',
|
heroTitles: [
|
||||||
heroDesc: '《交通运输工程造价咨询服务预算编制规范》\n(T/GDHS 017-2026)预算编制工具',
|
'众会易 | 算费真容易',
|
||||||
|
'众会易 | 算费不熬夜',
|
||||||
|
'众会易 | 算费不费力'
|
||||||
|
],
|
||||||
|
heroSubTitle: '',
|
||||||
|
heroDesc: '智算费用 即点即出 您的时间留给创造',
|
||||||
|
heroDescs: [
|
||||||
|
'智算费用 即点即出 您的时间留给创造。',
|
||||||
|
'智算费用 一键即出 您的时间留给创造。',
|
||||||
|
'智算费用 一键生成 您的时间留给创造。',
|
||||||
|
'众会易 真容易 不熬夜 不费力'
|
||||||
|
],
|
||||||
projectBudget: '项目预算',
|
projectBudget: '项目预算',
|
||||||
projectBudgetDesc: '适用于多合同段、项目级整体计算,支持导出/导入完整项目数据',
|
projectBudgetDesc: '适用于多合同段、项目级整体计算,支持导出/导入完整项目数据',
|
||||||
quickCalc: '单项速算',
|
quickCalc: '单项速算',
|
||||||
quickCalcDesc: '适用于单项服务试算,选择行业、咨询类型、工程专业,输入基数秒出结果',
|
quickCalcDesc: '适用于单项服务试算,选择行业、咨询类型、工程专业,输入基数秒出结果',
|
||||||
importData: '导入数据',
|
importData: '导入数据',
|
||||||
importDataDesc: '导入".zw"数据包,并自动新建一个项目用于恢复数据,不会覆盖现有项目',
|
importDataDesc: '导入".zw"数据包,并自动新建一个项目用于恢复数据,不会覆盖现有项目',
|
||||||
|
relatedFiles: '相关文件',
|
||||||
|
relatedFilesDesc: '在线查看、打印和下载与该计算器依据的收费文件、相关招标文件与合同文件范本',
|
||||||
|
viewFiles: '查看文件',
|
||||||
enter: '进入计算',
|
enter: '进入计算',
|
||||||
pickFile: '选择文件',
|
pickFile: '选择文件',
|
||||||
pickExisting: '选择已有项目'
|
pickExisting: '选择已有项目',
|
||||||
|
openFileSystem: '打开文件系统',
|
||||||
|
file: '文件系统',
|
||||||
|
fileDataDesc: '文件系统可查看相关收费文件、招标文件、合同文件、服务内容、工作要求'
|
||||||
},
|
},
|
||||||
disclaimer: {
|
disclaimer: {
|
||||||
link: '查看免责声明',
|
link: '查看免责声明',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user