Commit e5f96e5f authored by tao355667's avatar tao355667

feat: add floating contact actions

parent cc542d1a
Pipeline #466 canceled with stage
---
import { site } from "../data/site";
---
<dialog class="contact-dialog contact-dialog--form" data-contact-dialog aria-labelledby="contact-dialog-title"> <dialog class="contact-dialog contact-dialog--form" data-contact-dialog aria-labelledby="contact-dialog-title">
<div class="contact-dialog__panel"> <div class="contact-dialog__panel">
<button class="contact-dialog__close" type="button" data-contact-dialog-close aria-label="关闭领取弹窗">×</button> <button class="contact-dialog__close" type="button" data-contact-dialog-close aria-label="关闭领取弹窗">×</button>
<div class="contact-dialog__heading"> <div class="contact-dialog__heading">
<h2 id="contact-dialog-title">免费领取学情分析</h2> <h2 id="contact-dialog-title">免费领取 1对1 专属学情深度分析</h2>
<p>留下手机号,教务老师会尽快与您沟通孩子的年级、学科和当前学习情况。</p> <p>留下手机号,教务主任会尽快沟通孩子情况,也可添加教务主任微信。</p>
</div> </div>
<form class="consultation-form" data-consultation-form> <form class="consultation-form" data-consultation-form>
<label for="consultation-phone">家长手机号</label> <label for="consultation-phone">家长手机号</label>
...@@ -15,16 +18,33 @@ ...@@ -15,16 +18,33 @@
</div> </div>
</dialog> </dialog>
<dialog class="contact-dialog contact-dialog--phone" data-phone-dialog aria-labelledby="phone-dialog-title">
<div class="contact-dialog__panel">
<button class="contact-dialog__close" type="button" data-phone-dialog-close aria-label="关闭官方电话信息">×</button>
<div class="phone-dialog__heading">
<span>OFFICIAL CONTACT</span>
<h2 id="phone-dialog-title">官方电话</h2>
<p>课程咨询、教师匹配与服务安排,欢迎致电了解。</p>
</div>
<a class="phone-dialog__number" href={`tel:${site.contact.phone}`}>{site.contact.phoneDisplay}</a>
<div class="phone-dialog__details">
<span>服务时间:周一至周日 · 7×24 小时</span>
<a href={`mailto:${site.contact.email}`}>{site.contact.email}</a>
</div>
<a class="phone-dialog__call" href={`tel:${site.contact.phone}`}>立即拨打</a>
</div>
</dialog>
<dialog class="contact-dialog contact-dialog--teacher" data-teacher-dialog aria-labelledby="teacher-dialog-title"> <dialog class="contact-dialog contact-dialog--teacher" data-teacher-dialog aria-labelledby="teacher-dialog-title">
<div class="contact-dialog__panel"> <div class="contact-dialog__panel">
<button class="contact-dialog__close" type="button" data-teacher-dialog-close aria-label="关闭客服微信弹窗">×</button> <button class="contact-dialog__close" type="button" data-teacher-dialog-close aria-label="关闭教务主任微信弹窗">×</button>
<div class="teacher-dialog__heading"> <div class="teacher-dialog__heading">
<h2 id="teacher-dialog-title">添加客服微信</h2> <h2 id="teacher-dialog-title">添加教务主任微信</h2>
<p>手机号已提交,客服稍后会与您联系。您也可以扫码添加客服微信。</p> <p>手机号已提交,教务主任稍后会与您联系,也可扫码添加教务主任微信。</p>
</div> </div>
<div class="teacher-contact"> <div class="teacher-contact">
<div class="teacher-contact__identity"><strong data-teacher-name></strong><span data-teacher-title></span></div> <div class="teacher-contact__identity"><strong data-teacher-name></strong><span data-teacher-title></span></div>
<img data-teacher-qr alt="客服微信二维码" /> <img data-teacher-qr alt="教务主任微信二维码" />
<p class="teacher-wechat" data-teacher-wechat-wrap>微信号:<strong data-teacher-wechat></strong><button type="button" data-copy-teacher-wechat>复制</button></p> <p class="teacher-wechat" data-teacher-wechat-wrap>微信号:<strong data-teacher-wechat></strong><button type="button" data-copy-teacher-wechat>复制</button></p>
</div> </div>
</div> </div>
......
...@@ -18,5 +18,5 @@ const webPageLd = { "@context": "https://schema.org", "@type": "WebPage", "@id": ...@@ -18,5 +18,5 @@ const webPageLd = { "@context": "https://schema.org", "@type": "WebPage", "@id":
const pageLd = Array.isArray(jsonLd) ? jsonLd : [jsonLd]; const serializedLd = JSON.stringify([organizationLd, websiteLd, webPageLd, ...pageLd.filter((item) => Object.keys(item).length > 0)]).replace(/</g, "\\u003c"); const pageLd = Array.isArray(jsonLd) ? jsonLd : [jsonLd]; const serializedLd = JSON.stringify([organizationLd, websiteLd, webPageLd, ...pageLd.filter((item) => Object.keys(item).length > 0)]).replace(/</g, "\\u003c");
--- ---
<!doctype html><html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><meta name="theme-color" content="#1473E6" /><meta name="color-scheme" content="light" /><title>{metaTitle}</title><meta name="description" content={metaDescription} /><meta name="keywords" content={metaKeywords} /><meta name="robots" content={noindex ? "noindex,nofollow" : "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1"} /><link rel="canonical" href={canonical} />{prevUrl && <link rel="prev" href={prevUrl} />}{nextUrl && <link rel="next" href={nextUrl} />}<link rel="icon" href="/favicon.png" type="image/png" sizes="512x512" /><link rel="apple-touch-icon" href="/favicon.png" /><link rel="manifest" href="/site.webmanifest" /><link rel="alternate" hreflang="zh-CN" href={canonical} /><link rel="alternate" hreflang="x-default" href={canonical} /><meta property="og:type" content={ogType} /><meta property="og:title" content={metaTitle} /><meta property="og:description" content={metaDescription} /><meta property="og:url" content={canonical} /><meta property="og:image" content={ogImage} /><meta property="og:image:alt" content={imageAlt} /><meta property="og:site_name" content={site.brand.name} /><meta property="og:locale" content="zh_CN" />{publishedTime && <meta property="article:published_time" content={publishedTime} />}{modifiedTime && <meta property="article:modified_time" content={modifiedTime} />}{articleSection && <meta property="article:section" content={articleSection} />}{articleAuthor && <meta property="article:author" content={articleAuthor} />}<meta name="twitter:card" content="summary_large_image" /><meta name="twitter:title" content={metaTitle} /><meta name="twitter:description" content={metaDescription} /><meta name="twitter:image" content={ogImage} /><script type="application/ld+json" set:html={serializedLd} is:inline></script></head><body><a class="skip-link" href="#main-content">跳到主要内容</a><slot /><ContactDialog /><script is:inline> <!doctype html><html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><meta name="theme-color" content="#1473E6" /><meta name="color-scheme" content="light" /><title>{metaTitle}</title><meta name="description" content={metaDescription} /><meta name="keywords" content={metaKeywords} /><meta name="robots" content={noindex ? "noindex,nofollow" : "index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1"} /><link rel="canonical" href={canonical} />{prevUrl && <link rel="prev" href={prevUrl} />}{nextUrl && <link rel="next" href={nextUrl} />}<link rel="icon" href="/favicon.png" type="image/png" sizes="512x512" /><link rel="apple-touch-icon" href="/favicon.png" /><link rel="manifest" href="/site.webmanifest" /><link rel="alternate" hreflang="zh-CN" href={canonical} /><link rel="alternate" hreflang="x-default" href={canonical} /><meta property="og:type" content={ogType} /><meta property="og:title" content={metaTitle} /><meta property="og:description" content={metaDescription} /><meta property="og:url" content={canonical} /><meta property="og:image" content={ogImage} /><meta property="og:image:alt" content={imageAlt} /><meta property="og:site_name" content={site.brand.name} /><meta property="og:locale" content="zh_CN" />{publishedTime && <meta property="article:published_time" content={publishedTime} />}{modifiedTime && <meta property="article:modified_time" content={modifiedTime} />}{articleSection && <meta property="article:section" content={articleSection} />}{articleAuthor && <meta property="article:author" content={articleAuthor} />}<meta name="twitter:card" content="summary_large_image" /><meta name="twitter:title" content={metaTitle} /><meta name="twitter:description" content={metaDescription} /><meta name="twitter:image" content={ogImage} /><script type="application/ld+json" set:html={serializedLd} is:inline></script></head><body><a class="skip-link" href="#main-content">跳到主要内容</a><slot /><ContactDialog /><script is:inline>
const header=document.querySelector('[data-header]');const syncHeader=()=>header?.classList.toggle('is-scrolled',scrollY>12);addEventListener('scroll',syncHeader,{passive:true});syncHeader();const observer='IntersectionObserver'in window?new IntersectionObserver(entries=>entries.forEach(entry=>{if(entry.isIntersecting){entry.target.classList.add('is-visible');observer.unobserve(entry.target)}}),{threshold:.08}):null;document.querySelectorAll('[data-reveal]').forEach(el=>observer?observer.observe(el):el.classList.add('is-visible'));document.querySelectorAll('[data-mobile-nav] a').forEach(link=>link.addEventListener('click',()=>link.closest('details')?.removeAttribute('open')));const dialog=document.querySelector('[data-contact-dialog]');const teacherDialog=document.querySelector('[data-teacher-dialog]');const form=dialog?.querySelector('[data-consultation-form]');const copyWechat=teacherDialog?.querySelector('[data-copy-teacher-wechat]');const resetConsultation=()=>{form?.reset();const error=dialog?.querySelector('[data-consultation-error]');if(error)error.textContent=''};document.addEventListener('click',event=>{const trigger=event.target.closest('[data-open-contact]');if(!dialog||!trigger)return;event.preventDefault();resetConsultation();dialog.showModal();dialog.querySelector('#consultation-phone')?.focus()});dialog?.querySelector('[data-contact-dialog-close]')?.addEventListener('click',()=>dialog.close());teacherDialog?.querySelector('[data-teacher-dialog-close]')?.addEventListener('click',()=>teacherDialog.close());[dialog,teacherDialog].forEach(item=>item?.addEventListener('click',event=>{if(event.target===item)item.close()}));copyWechat?.addEventListener('click',async()=>{const value=teacherDialog.querySelector('[data-teacher-wechat]').textContent.trim();if(!value)return;try{await navigator.clipboard.writeText(value);copyWechat.textContent='已复制';setTimeout(()=>{copyWechat.textContent='复制'},1600)}catch{copyWechat.textContent='复制失败';setTimeout(()=>{copyWechat.textContent='复制'},1600)}});form?.addEventListener('submit',async event=>{event.preventDefault();const phone=String(new FormData(form).get('phone')||'').replace(/\s+/g,'');const error=dialog.querySelector('[data-consultation-error]');const submit=form.querySelector('button[type="submit"]');error.textContent='';if(!/^1[3-9]\d{9}$/.test(phone)){error.textContent='请输入正确的 11 位手机号';return}submit.disabled=true;submit.textContent='正在领取…';try{const response=await fetch('/api/cms/consultations/claim',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({phone,source:'免费领取学情分析',page:location.href})});const result=await response.json();if(!response.ok)throw new Error(result.error||'提交失败,请稍后重试');teacherDialog.querySelector('[data-teacher-name]').textContent=result.teacher.name;teacherDialog.querySelector('[data-teacher-title]').textContent=result.teacher.title;teacherDialog.querySelector('[data-teacher-qr]').src=result.teacher.qrCodeUrl;teacherDialog.querySelector('[data-teacher-wechat]').textContent=result.teacher.wechat||'';teacherDialog.querySelector('[data-teacher-wechat-wrap]').classList.toggle('hidden',!result.teacher.wechat);copyWechat.textContent='复制';dialog.close();teacherDialog.showModal()}catch(requestError){error.textContent=requestError.message}finally{submit.disabled=false;submit.textContent='立即领取'}}); const header=document.querySelector('[data-header]');const syncHeader=()=>header?.classList.toggle('is-scrolled',scrollY>12);addEventListener('scroll',syncHeader,{passive:true});syncHeader();const observer='IntersectionObserver'in window?new IntersectionObserver(entries=>entries.forEach(entry=>{if(entry.isIntersecting){entry.target.classList.add('is-visible');observer.unobserve(entry.target)}}),{threshold:.08}):null;document.querySelectorAll('[data-reveal]').forEach(el=>observer?observer.observe(el):el.classList.add('is-visible'));document.querySelectorAll('[data-mobile-nav] a').forEach(link=>link.addEventListener('click',()=>link.closest('details')?.removeAttribute('open')));const dialog=document.querySelector('[data-contact-dialog]');const phoneDialog=document.querySelector('[data-phone-dialog]');const teacherDialog=document.querySelector('[data-teacher-dialog]');const phoneTrigger=document.querySelector('[data-phone-contact]');const form=dialog?.querySelector('[data-consultation-form]');const copyWechat=teacherDialog?.querySelector('[data-copy-teacher-wechat]');const resetConsultation=()=>{form?.reset();const error=dialog?.querySelector('[data-consultation-error]');if(error)error.textContent=''};document.addEventListener('click',event=>{const trigger=event.target.closest('[data-open-contact]');if(!dialog||!trigger)return;event.preventDefault();resetConsultation();dialog.showModal();dialog.querySelector('#consultation-phone')?.focus()});phoneTrigger?.addEventListener('click',event=>{if(matchMedia('(max-width:760px)').matches)return;event.preventDefault();phoneDialog?.showModal()});dialog?.querySelector('[data-contact-dialog-close]')?.addEventListener('click',()=>dialog.close());phoneDialog?.querySelector('[data-phone-dialog-close]')?.addEventListener('click',()=>phoneDialog.close());teacherDialog?.querySelector('[data-teacher-dialog-close]')?.addEventListener('click',()=>teacherDialog.close());[dialog,phoneDialog,teacherDialog].forEach(item=>item?.addEventListener('click',event=>{if(event.target===item)item.close()}));copyWechat?.addEventListener('click',async()=>{const value=teacherDialog.querySelector('[data-teacher-wechat]').textContent.trim();if(!value)return;try{await navigator.clipboard.writeText(value);copyWechat.textContent='已复制';setTimeout(()=>{copyWechat.textContent='复制'},1600)}catch{copyWechat.textContent='复制失败';setTimeout(()=>{copyWechat.textContent='复制'},1600)}});form?.addEventListener('submit',async event=>{event.preventDefault();const phone=String(new FormData(form).get('phone')||'').replace(/\s+/g,'');const error=dialog.querySelector('[data-consultation-error]');const submit=form.querySelector('button[type="submit"]');error.textContent='';if(!/^1[3-9]\d{9}$/.test(phone)){error.textContent='请输入正确的 11 位手机号';return}submit.disabled=true;submit.textContent='正在领取…';try{const response=await fetch('/api/cms/consultations/claim',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({phone,source:'免费领取学情分析',page:location.href})});const result=await response.json();if(!response.ok)throw new Error(result.error||'提交失败,请稍后重试');teacherDialog.querySelector('[data-teacher-name]').textContent=result.teacher.name;teacherDialog.querySelector('[data-teacher-title]').textContent=result.teacher.title;teacherDialog.querySelector('[data-teacher-qr]').src=result.teacher.qrCodeUrl;teacherDialog.querySelector('[data-teacher-wechat]').textContent=result.teacher.wechat||'';teacherDialog.querySelector('[data-teacher-wechat-wrap]').classList.toggle('hidden',!result.teacher.wechat);copyWechat.textContent='复制';dialog.close();teacherDialog.showModal()}catch(requestError){error.textContent=requestError.message}finally{submit.disabled=false;submit.textContent='立即领取'}});
</script></body></html> </script><a class="floating-phone" href={`tel:${site.contact.phone}`} data-phone-contact aria-label={`官方电话 ${site.contact.phoneDisplay},点击查看联系信息或拨号`}><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7.2 4.5 5.4 5.8c-.8.6-.9 1.7-.6 2.6 1.7 5.2 5.1 8.6 10.3 10.3.9.3 2 0 2.6-.6l1.3-1.8c.4-.5.3-1.2-.2-1.6l-2.7-2.2c-.5-.4-1.2-.3-1.6.1l-1.1 1.3c-2-1-3.7-2.7-4.7-4.7l1.3-1.1c.4-.4.5-1.1.1-1.6L8.8 4.7c-.4-.5-1.1-.6-1.6-.2Z" /></svg><span>官方电话</span></a><button class="floating-consultation" type="button" data-open-contact aria-label="在线咨询,打开学情分析表单"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.5h16v11H9l-5 3v-14Z" /></svg><span>在线咨询</span><i aria-hidden="true">1</i></button></body></html>
...@@ -131,6 +131,12 @@ ...@@ -131,6 +131,12 @@
.contact-hero .breadcrumb,.contact-hero .breadcrumb a{color:rgba(255,255,255,.86)} .contact-hero .breadcrumb,.contact-hero .breadcrumb a{color:rgba(255,255,255,.86)}
.contact-hero .breadcrumb a:hover{color:#fff} .contact-hero .breadcrumb a:hover{color:#fff}
/* 固定咨询入口:让在线咨询始终保持在页面可操作范围内。 */
.floating-consultation{position:fixed;right:24px;bottom:24px;z-index:90;display:inline-flex;width:198px;height:62px;align-items:center;justify-content:center;gap:11px;padding:0 22px;border:0;border-radius:32px;color:#fff;background:#1d2b49;box-shadow:0 12px 26px rgba(20,37,68,.2);cursor:pointer;font-size:20px;font-weight:700;letter-spacing:.02em;transition:transform .2s,box-shadow .2s,background .2s}.floating-consultation svg{width:25px;height:25px;fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.8}.floating-consultation:hover{background:#14213a;box-shadow:0 16px 30px rgba(20,37,68,.27);transform:translateY(-2px)}.floating-consultation:focus-visible{outline:3px solid rgba(20,115,230,.42);outline-offset:4px}.floating-consultation i{position:absolute;top:-7px;right:-4px;display:grid;width:28px;height:28px;place-items:center;border-radius:50%;color:#fff;background:#ff4f5d;font-size:14px;font-style:normal;font-weight:700;box-shadow:0 2px 6px rgba(255,79,93,.22)}
.floating-phone{position:fixed;right:24px;bottom:98px;z-index:90;display:inline-flex;width:198px;height:62px;align-items:center;justify-content:center;gap:11px;padding:0 22px;border:0;border-radius:32px;color:#fff;background:#ff7900;box-shadow:0 12px 26px rgba(204,92,0,.2);font-size:20px;font-weight:700;letter-spacing:.02em;text-decoration:none;transition:transform .2s,box-shadow .2s,background .2s}.floating-phone svg{width:25px;height:25px;fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.8}.floating-phone:hover{background:#e86d00;box-shadow:0 16px 30px rgba(204,92,0,.27);transform:translateY(-2px)}.floating-phone:focus-visible{outline:3px solid rgba(255,121,0,.35);outline-offset:4px}
.contact-dialog--phone{width:min(440px,calc(100% - 28px))}.contact-dialog--phone .contact-dialog__panel{padding:32px 34px 28px}.phone-dialog__heading>span{display:block;margin-bottom:8px;color:#e86d00;font-size:10px;font-weight:800;letter-spacing:.16em}.phone-dialog__heading h2{margin:0 0 8px;color:#1e3248;font-size:29px}.phone-dialog__heading p{margin:0;color:#66798a;font-size:14px;line-height:1.7}.phone-dialog__number{display:block;margin:24px 0 18px;color:#1e3248;font-size:30px;font-weight:750;letter-spacing:.04em}.phone-dialog__details{display:grid;gap:5px;padding:14px 0;border-top:1px solid #e2eaf0;border-bottom:1px solid #e2eaf0;color:#718393;font-size:12px;line-height:1.6}.phone-dialog__details a{color:var(--brand-blue)}.phone-dialog__call{display:flex;min-height:44px;margin-top:20px;align-items:center;justify-content:center;border-radius:9px;color:#fff;background:#ff7900;font-size:14px;font-weight:700;transition:background .2s,transform .2s}.phone-dialog__call:hover{background:#e86d00;transform:translateY(-1px)}
@media(max-width:760px){.floating-phone{right:16px;bottom:150px;width:148px;height:56px;gap:9px;padding:0 18px;font-size:18px}.floating-phone svg{width:23px;height:23px}.floating-consultation{right:16px;bottom:84px;width:148px;height:56px;gap:9px;padding:0 18px;font-size:18px}.floating-consultation svg{width:23px;height:23px}.floating-consultation i{top:-6px;right:-3px;width:26px;height:26px;font-size:13px}}
@media(max-width:1080px){ @media(max-width:1080px){
.subject-stage-grid article{min-height:365px} .subject-stage-grid article{min-height:365px}
.about-brand-layout{gap:48px} .about-brand-layout{gap:48px}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment