Some checks failed
Build and Deploy Front / deploy (push) Has been cancelled
欢迎区/侧栏/聊天槽统一暖纸拟物;跟进改仓储代发;波浪降扰动关视差减少眩晕。 Co-authored-by: Cursor <cursoragent@cursor.com>
448 lines
12 KiB
Vue
448 lines
12 KiB
Vue
<template>
|
|
<div
|
|
class="official-site"
|
|
:class="{
|
|
'is-routes-open': panel === 'routes',
|
|
'is-services-open': panel === 'services',
|
|
'is-topic-open': panel === 'pricing' || panel === 'about' || panel === 'contact',
|
|
}"
|
|
@click.capture="onOfficialLoginCapture"
|
|
>
|
|
<SiteHeader
|
|
:nav="nav"
|
|
:is-zh="isZh"
|
|
:login-url="loginUrl"
|
|
@home="onHome"
|
|
@nav="onNav"
|
|
@toggle-lang="toggleLang"
|
|
@open-nav="openMobileNav"
|
|
@close-nav="closeMobileNav"
|
|
@warm-login="warmupOfficialLogin"
|
|
/>
|
|
<div id="official-site-mount">
|
|
<SiteAdvisorShell
|
|
v-model="draft"
|
|
:is-zh="isZh"
|
|
:chatting="chatting"
|
|
:history-open="historyOpen"
|
|
:busy="busy"
|
|
:login-url="loginUrl"
|
|
:show-new-chat-tip="showNewChatTip"
|
|
:messages="messages"
|
|
:typing="typing"
|
|
:threads="threads"
|
|
:active-thread-id="activeThreadId"
|
|
:routes-pending="routesPending"
|
|
@submit="onSubmit"
|
|
@intent="advisor.askIntent"
|
|
@human="onNav('contact')"
|
|
@wechat="openWecom"
|
|
@prefill="draft = $event"
|
|
@new-chat="onHome"
|
|
@toggle-history="historyOpen = !historyOpen"
|
|
@close-history="historyOpen = false"
|
|
@warm-login="warmupOfficialLogin"
|
|
@open-routes="openRoutes"
|
|
@open-panel="openPanel"
|
|
@switch-thread="onSwitchThread"
|
|
@choice="onChoice"
|
|
/>
|
|
</div>
|
|
<Transition name="site-fade">
|
|
<button
|
|
v-if="panel"
|
|
type="button"
|
|
class="site-panel-backdrop"
|
|
aria-hidden="true"
|
|
tabindex="-1"
|
|
@click="closePanel"
|
|
></button>
|
|
</Transition>
|
|
<Transition name="site-sheet">
|
|
<SiteRoutesPanel
|
|
v-if="panel === 'routes'"
|
|
key="routes"
|
|
:is-zh="isZh"
|
|
:dest-id="routesDest"
|
|
@close="closePanel"
|
|
@ask="askAdvisor"
|
|
/>
|
|
<SiteServicesPanel
|
|
v-else-if="panel === 'services'"
|
|
key="services"
|
|
:is-zh="isZh"
|
|
@close="closePanel"
|
|
@ask="askAdvisor"
|
|
/>
|
|
<SiteTopicPanel
|
|
v-else-if="panel === 'pricing' || panel === 'about' || panel === 'contact'"
|
|
:key="panel"
|
|
:topic="panel"
|
|
:is-zh="isZh"
|
|
:wecom-url="WECOM_KF_URL"
|
|
@close="closePanel"
|
|
@ask="askAdvisor"
|
|
@wechat="onTopicWechat"
|
|
@open-panel="openPanel"
|
|
/>
|
|
</Transition>
|
|
<SiteWecomModal :open="wecomOpen" :is-zh="isZh" :url="WECOM_KF_URL" @close="wecomOpen = false" />
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
import SiteHeader from './SiteHeader.vue';
|
|
import SiteAdvisorShell from './SiteAdvisorShell.vue';
|
|
import SiteRoutesPanel from './SiteRoutesPanel.vue';
|
|
import SiteServicesPanel from './SiteServicesPanel.vue';
|
|
import SiteTopicPanel from './SiteTopicPanel.vue';
|
|
import SiteWecomModal from './SiteWecomModal.vue';
|
|
import {
|
|
goOfficialLoginFromHref,
|
|
officialLoginUrl,
|
|
warmupOfficialLogin,
|
|
} from './lib/site-urls';
|
|
import { shouldOpenWecomDirect, WECOM_KF_URL } from './lib/site-wecom';
|
|
import { useSiteAdvisor } from './useSiteAdvisor';
|
|
import './styles/vendor.css';
|
|
import './styles/icons.css';
|
|
import './styles/main.css';
|
|
import './styles/ai-advisor.css';
|
|
|
|
type PanelId = 'routes' | 'services' | 'pricing' | 'about' | 'contact' | null;
|
|
const NAV_PANELS: PanelId[] = ['routes', 'services', 'pricing', 'about', 'contact'];
|
|
|
|
const TITLE = 'IDE - 跨境物流,全球即递';
|
|
const DESCRIPTION =
|
|
'IDE 面向中小卖家的跨境物流。全球数字化一站式跨境物流服务商。注册登记后即可询价,一口价不砍,专属顾问跟到签收。';
|
|
|
|
const router = useRouter();
|
|
const lang = ref<'zh' | 'en'>('zh');
|
|
const nav = ref('home');
|
|
const panel = ref<PanelId>(null);
|
|
const routesDest = ref('all');
|
|
const draft = ref('');
|
|
const historyOpen = ref(false);
|
|
const wecomOpen = ref(false);
|
|
const isZh = computed(() => lang.value === 'zh');
|
|
const loginUrl = computed(() => officialLoginUrl());
|
|
|
|
/** 顾问卡片等其它「去登录」链接统一走 SPA 翻页 */
|
|
function onOfficialLoginCapture(e: MouseEvent) {
|
|
const target = e.target as Element | null;
|
|
const anchor = target?.closest?.('a[href]') as HTMLAnchorElement | null;
|
|
if (!anchor || anchor.classList.contains('site-nav-login')) return;
|
|
const href = anchor.getAttribute('href') || '';
|
|
if (!href.includes('from=official-site')) return;
|
|
goOfficialLoginFromHref(router, href, e);
|
|
}
|
|
|
|
function focusComposer() {
|
|
const input = document.querySelector('.ai-composer-input') as HTMLTextAreaElement | null;
|
|
input?.focus();
|
|
}
|
|
|
|
const advisor = useSiteAdvisor({
|
|
lang,
|
|
openServices: () => openPanel('services'),
|
|
focusComposer,
|
|
});
|
|
|
|
const {
|
|
messages,
|
|
threads,
|
|
activeThreadId,
|
|
busy,
|
|
typing,
|
|
chatting,
|
|
showNewChatTip,
|
|
routesPending,
|
|
} = advisor;
|
|
|
|
let previousTitle = '';
|
|
|
|
function readLang(): 'zh' | 'en' {
|
|
try {
|
|
const saved = localStorage.getItem('ide-language');
|
|
if (saved === 'zh' || saved === 'en') return saved;
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
return 'zh';
|
|
}
|
|
|
|
function ensureMeta(name: string, content: string) {
|
|
let node = document.querySelector(`meta[name="${name}"]`);
|
|
if (!node) {
|
|
node = document.createElement('meta');
|
|
node.setAttribute('name', name);
|
|
document.head.appendChild(node);
|
|
}
|
|
node.setAttribute('content', content);
|
|
}
|
|
|
|
function ensureOg(property: string, content: string) {
|
|
let node = document.querySelector(`meta[property="${property}"]`);
|
|
if (!node) {
|
|
node = document.createElement('meta');
|
|
node.setAttribute('property', property);
|
|
document.head.appendChild(node);
|
|
}
|
|
node.setAttribute('content', content);
|
|
}
|
|
|
|
function applyMeta() {
|
|
document.title = TITLE;
|
|
ensureMeta('description', DESCRIPTION);
|
|
ensureMeta(
|
|
'keywords',
|
|
'IDE, 跨境物流, 中小卖家, 9610, 疑难单, 出口退税, 欧美专线, 东南亚专线',
|
|
);
|
|
const origin = window.location.origin;
|
|
ensureOg('og:title', TITLE);
|
|
ensureOg('og:description', DESCRIPTION);
|
|
ensureOg('og:image', `${origin}/site/img/og-cover.png`);
|
|
ensureOg('og:url', `${origin}/`);
|
|
ensureOg('og:type', 'website');
|
|
}
|
|
|
|
function openMobileNav() {
|
|
document.body.classList.add('mobile-nav-active');
|
|
document.querySelectorAll('.mobile-nav-show').forEach((n) => n.classList.add('d-none'));
|
|
document.querySelectorAll('.mobile-nav-hide').forEach((n) => n.classList.remove('d-none'));
|
|
}
|
|
|
|
function closeMobileNav() {
|
|
document.body.classList.remove('mobile-nav-active');
|
|
document.querySelectorAll('.mobile-nav-show').forEach((n) => n.classList.remove('d-none'));
|
|
document.querySelectorAll('.mobile-nav-hide').forEach((n) => n.classList.add('d-none'));
|
|
}
|
|
|
|
function syncHash(id: string) {
|
|
const next = id && id !== 'home' ? `#${id}` : location.pathname + location.search;
|
|
if (id === 'home') {
|
|
if (location.hash) history.replaceState(null, '', location.pathname + location.search);
|
|
return;
|
|
}
|
|
if (location.hash !== next) history.replaceState(null, '', next);
|
|
}
|
|
|
|
function openPanel(id: Exclude<PanelId, null>) {
|
|
closeMobileNav();
|
|
historyOpen.value = false;
|
|
if (id !== 'routes') routesDest.value = 'all';
|
|
panel.value = id;
|
|
nav.value = id;
|
|
syncHash(id);
|
|
}
|
|
|
|
function openRoutes(dest?: string) {
|
|
advisor.clearRoutesPending();
|
|
routesDest.value = dest || 'all';
|
|
openPanel('routes');
|
|
}
|
|
|
|
function closePanel(id?: string) {
|
|
if (id && panel.value && panel.value !== id) return;
|
|
if (!panel.value) return;
|
|
panel.value = null;
|
|
nav.value = 'home';
|
|
syncHash('home');
|
|
}
|
|
|
|
function onHome() {
|
|
closeMobileNav();
|
|
closePanel();
|
|
historyOpen.value = false;
|
|
nav.value = 'home';
|
|
syncHash('home');
|
|
advisor.startNewChat();
|
|
}
|
|
|
|
function onNav(id: string) {
|
|
closeMobileNav();
|
|
if (id === 'home') {
|
|
onHome();
|
|
return;
|
|
}
|
|
if (NAV_PANELS.includes(id as PanelId)) {
|
|
openPanel(id as Exclude<PanelId, null>);
|
|
return;
|
|
}
|
|
closePanel();
|
|
nav.value = id;
|
|
syncHash(id);
|
|
}
|
|
|
|
function askAdvisor(payload: { text: string; intentId: string }) {
|
|
const fromRoutes = panel.value === 'routes';
|
|
closePanel();
|
|
draft.value = '';
|
|
advisor.ask(payload.text, { intentId: payload.intentId, fromRoutes });
|
|
}
|
|
|
|
function onSubmit() {
|
|
const text = draft.value.trim();
|
|
if (!text) return;
|
|
draft.value = '';
|
|
advisor.ask(text);
|
|
}
|
|
|
|
function onChoice(payload: { id: string; label: string; flowId: string }) {
|
|
draft.value = '';
|
|
advisor.ask(payload.label, { kind: 'choice', flowId: payload.flowId, choice: payload.id });
|
|
}
|
|
|
|
function onSwitchThread(id: string) {
|
|
advisor.switchThread(id);
|
|
historyOpen.value = false;
|
|
}
|
|
|
|
function openWecom() {
|
|
if (shouldOpenWecomDirect()) {
|
|
window.location.href = WECOM_KF_URL;
|
|
return;
|
|
}
|
|
wecomOpen.value = true;
|
|
}
|
|
|
|
function onTopicWechat() {
|
|
if (shouldOpenWecomDirect()) {
|
|
window.location.href = WECOM_KF_URL;
|
|
return;
|
|
}
|
|
wecomOpen.value = true;
|
|
}
|
|
|
|
function toggleLang() {
|
|
const next = lang.value === 'zh' ? 'en' : 'zh';
|
|
lang.value = next;
|
|
localStorage.setItem('ide-language', next);
|
|
}
|
|
|
|
function onKeydown(e: KeyboardEvent) {
|
|
if (e.key === 'Escape') {
|
|
closePanel();
|
|
historyOpen.value = false;
|
|
wecomOpen.value = false;
|
|
}
|
|
}
|
|
|
|
function syncHeaderOffset() {
|
|
const header = document.getElementById('header');
|
|
const height = header ? Math.round(header.getBoundingClientRect().height) : 72;
|
|
document.documentElement.style.setProperty('--ai-header-h', `${height}px`);
|
|
}
|
|
|
|
watch(panel, (id) => {
|
|
document.body.classList.toggle('site-panel-routes', id === 'routes');
|
|
document.body.classList.toggle('site-panel-services', id === 'services');
|
|
});
|
|
|
|
onMounted(() => {
|
|
previousTitle = document.title;
|
|
lang.value = readLang();
|
|
applyMeta();
|
|
document.documentElement.classList.add('command-home');
|
|
document.body.classList.add('command-home');
|
|
window.addEventListener('keydown', onKeydown);
|
|
window.addEventListener('resize', syncHeaderOffset);
|
|
syncHeaderOffset();
|
|
const hash = (window.location.hash || '').replace('#', '');
|
|
if (hash === '9610' || hash === 'services') openPanel('services');
|
|
else if (NAV_PANELS.includes(hash as PanelId)) openPanel(hash as Exclude<PanelId, null>);
|
|
else if (hash) nav.value = hash;
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('keydown', onKeydown);
|
|
window.removeEventListener('resize', syncHeaderOffset);
|
|
document.body.classList.remove('site-panel-routes', 'site-panel-services');
|
|
document.documentElement.classList.remove('command-home', 'app-boot-site');
|
|
closeMobileNav();
|
|
if (previousTitle) document.title = previousTitle;
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.official-site {
|
|
min-height: 100vh;
|
|
}
|
|
</style>
|
|
|
|
<style>
|
|
body.command-home #app {
|
|
background: transparent;
|
|
min-height: 100%;
|
|
}
|
|
|
|
.site-panel-backdrop {
|
|
display: block;
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 995;
|
|
border: none;
|
|
background: color-mix(in srgb, var(--ai-bg, #f0f3f4) 55%, rgba(18, 24, 32, 0.18));
|
|
backdrop-filter: blur(6px) saturate(140%);
|
|
-webkit-backdrop-filter: blur(6px) saturate(140%);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.site-fade-enter-active,
|
|
.site-fade-leave-active {
|
|
transition: opacity 0.32s cubic-bezier(0.22, 1, 0.36, 1);
|
|
}
|
|
|
|
.site-fade-enter-from,
|
|
.site-fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
.site-sheet-enter-active,
|
|
.site-sheet-leave-active {
|
|
transition:
|
|
opacity 0.34s cubic-bezier(0.22, 1, 0.36, 1),
|
|
transform 0.4s cubic-bezier(0.22, 1, 0.36, 1),
|
|
filter 0.34s cubic-bezier(0.22, 1, 0.36, 1);
|
|
}
|
|
|
|
.site-sheet-leave-active {
|
|
pointer-events: none;
|
|
}
|
|
|
|
.site-sheet-enter-from {
|
|
opacity: 0;
|
|
filter: blur(2px);
|
|
transform: translateX(-50%) translateY(-10px);
|
|
}
|
|
|
|
.site-sheet-leave-to {
|
|
opacity: 0;
|
|
filter: blur(1px);
|
|
transform: translateX(-50%) translateY(-6px);
|
|
}
|
|
|
|
@media (max-width: 780px) {
|
|
.site-sheet-enter-from {
|
|
filter: none;
|
|
transform: translateY(16px);
|
|
}
|
|
|
|
.site-sheet-leave-to {
|
|
filter: none;
|
|
transform: translateY(10px);
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.site-fade-enter-active,
|
|
.site-fade-leave-active,
|
|
.site-sheet-enter-active,
|
|
.site-sheet-leave-active {
|
|
transition-duration: 0.01ms;
|
|
}
|
|
}
|
|
</style>
|