feat:页面优化

This commit is contained in:
Jafeng 2026-04-30 13:34:02 +08:00
parent 17cb1efcd0
commit c5c9cb5cee
10 changed files with 153 additions and 56 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -60,7 +60,10 @@ const handleCategoryKeydown = (event, index) => {
</aside>
<div id="case-panel" class="case-grid" role="tabpanel" :aria-label="active">
<article v-for="item in filteredCases" :key="`${active}-${item.name}`" class="case-card">
<div :class="['case-logo', `case-logo--${item.tone}`]">{{ item.short }}</div>
<div :class="['case-logo-slot', { 'case-logo-slot--placeholder': !item.logo }]">
<img v-if="item.logo" :src="item.logo" :alt="`${item.name.replace(/\n/g, '')} logo`" />
<span v-else :class="['case-logo-fallback', `case-logo--${item.tone}`]">{{ item.short }}</span>
</div>
<p>{{ item.name }}</p>
</article>
</div>

View File

@ -1,4 +1,5 @@
<script setup>
import IconMark from './IconMark.vue'
import QrPlaceholder from './QrPlaceholder.vue'
import SectionTitle from './SectionTitle.vue'
</script>
@ -15,21 +16,27 @@ import SectionTitle from './SectionTitle.vue'
rel="noopener noreferrer"
aria-label="在地图中打开办公地址"
>
<span class="address-icon">📍</span>
<span class="address-icon">
<IconMark name="map-pin" :size="22" />
</span>
<strong>办公地址</strong>
<span>杭州市西湖区翠苑街道塘苗路1号4号楼2楼</span>
</a>
<div class="contact-manager-grid">
<article class="manager-card">
<div class="manager-copy">
<span class="manager-icon">👤</span>
<span class="manager-icon">
<IconMark name="user" :size="22" />
</span>
<p><strong>客户成功经理王经理</strong><br /></p>
</div>
<QrPlaceholder label="王经理个人码" small />
</article>
<article class="manager-card">
<div class="manager-copy">
<span class="manager-icon">👤</span>
<span class="manager-icon">
<IconMark name="user" :size="22" />
</span>
<p><strong>客户成功经理罗经理</strong><br /></p>
</div>
<QrPlaceholder label="罗经理个人码" small />

View File

@ -1,6 +1,7 @@
<script setup>
import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
import { navItems } from '../data/homepage'
import IconMark from './IconMark.vue'
const activeTarget = ref(navItems[0]?.target ?? '')
const isMenuOpen = ref(false)
@ -99,9 +100,7 @@ onBeforeUnmount(() => {
:aria-label="isMenuOpen ? '收起主导航' : '展开主导航'"
@click="toggleMenu"
>
<span></span>
<span></span>
<span></span>
<IconMark :name="isMenuOpen ? 'x' : 'menu'" :size="24" />
</button>
<nav
id="main-navigation"

View File

@ -4,8 +4,7 @@
<div class="hero-bg-orb hero-bg-orb--right"></div>
<div class="container hero-grid">
<div class="hero-copy">
<p class="eyebrow">医疗服务数智化升级服务商</p>
<h1>医疗行业患者全周期<br />管理服务数智化升级服务</h1>
<h1><span class="hero-title-nowrap">医疗行业患者全周期管理服务</span><br />数智化升级服务商</h1>
<div class="hero-badge">专注于医疗服务提质提效增收</div>
<article id="company" class="intro-card glass-card">
<p>

View File

@ -6,27 +6,68 @@ defineProps({
</script>
<template>
<svg class="icon-mark" :width="size" :height="size" viewBox="0 0 48 48" fill="none" aria-hidden="true">
<svg
class="icon-mark"
:width="size"
:height="size"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.25"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<template v-if="name === 'user-flow'">
<circle cx="24" cy="16" r="7" stroke="currentColor" stroke-width="3" />
<path d="M12 37c2.4-8.2 7-12 12-12s9.6 3.8 12 12" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
<path d="M9 24h5m20 0h5" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
<path d="M18 21a8 8 0 0 0-16 0" />
<circle cx="10" cy="8" r="5" />
<path d="M22 20c0-3.37-2-6.5-4.5-8" />
<path d="M17 3.13a5 5 0 0 1 0 9.74" />
</template>
<template v-else-if="name === 'clipboard'">
<rect x="12" y="10" width="24" height="30" rx="5" stroke="currentColor" stroke-width="3" />
<path d="M18 8h12l2 5H16l2-5Z" fill="currentColor" />
<path d="M18 23h12M18 31h8" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
<rect width="8" height="4" x="8" y="2" rx="1" ry="1" />
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" />
<path d="M9 12h6" />
<path d="M9 16h6" />
</template>
<template v-else-if="name === 'chart'">
<path d="M12 38h26" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
<rect x="15" y="25" width="5" height="10" rx="2" fill="currentColor" />
<rect x="24" y="17" width="5" height="18" rx="2" fill="currentColor" opacity="0.76" />
<rect x="33" y="10" width="5" height="25" rx="2" fill="currentColor" opacity="0.52" />
<path d="M3 3v18h18" />
<path d="M18 17V9" />
<path d="M13 17V5" />
<path d="M8 17v-3" />
</template>
<template v-else-if="name === 'ai'">
<path d="M12 8V4H8" />
<rect width="16" height="12" x="4" y="8" rx="2" />
<path d="M2 14h2" />
<path d="M20 14h2" />
<path d="M15 13v2" />
<path d="M9 13v2" />
</template>
<template v-else-if="name === 'check'">
<path d="M20 6 9 17l-5-5" />
</template>
<template v-else-if="name === 'map-pin'">
<path d="M20 10c0 4.99-5.54 10.19-7.4 11.8a1 1 0 0 1-1.2 0C9.54 20.19 4 14.99 4 10a8 8 0 0 1 16 0" />
<circle cx="12" cy="10" r="3" />
</template>
<template v-else-if="name === 'user'">
<path d="M18 20a6 6 0 0 0-12 0" />
<circle cx="12" cy="10" r="4" />
</template>
<template v-else-if="name === 'menu'">
<path d="M4 6h16" />
<path d="M4 12h16" />
<path d="M4 18h16" />
</template>
<template v-else-if="name === 'x'">
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</template>
<template v-else>
<path d="M15 32c-4-3-6-8-4-13 2-7 8-11 15-10 8 1 13 7 13 15 0 6-3 11-8 14" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
<path d="M19 25h4l3-8 3 16 3-8h4" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
<path d="M18 39h13" stroke="currentColor" stroke-width="3" stroke-linecap="round" />
<circle cx="12" cy="12" r="10" />
<path d="M12 8v4" />
<path d="M12 16h.01" />
</template>
</svg>
</template>

View File

@ -18,7 +18,9 @@ import SectionTitle from './SectionTitle.vue'
</div>
<ul class="feature-list">
<li v-for="item in feature.items" :key="item">
<span class="check-dot"></span>
<span class="check-dot">
<IconMark name="check" :size="11" />
</span>
{{ item }}
</li>
</ul>

View File

@ -40,7 +40,7 @@ const handleTabKeydown = (event, index) => {
<template>
<section class="solution-section page-section page-section--compact">
<div class="container">
<div class="solution-tabs" role="tablist" aria-label="版本切换">
<div class="solution-tabs solution-tabs--mobile" role="tablist" aria-label="版本切换">
<button
v-for="(solution, index) in solutions"
:key="solution.key"
@ -59,7 +59,28 @@ const handleTabKeydown = (event, index) => {
<span class="tab-indicator" :style="{ transform: `translateX(${activeIndex * 100}%)` }"></span>
</div>
<div class="solution-grid">
<div class="solution-grid solution-grid--desktop">
<article
v-for="solution in solutions"
:key="solution.key"
class="solution-card"
>
<div class="solution-card-top">
<span class="solution-kicker">{{ solution.tab }}</span>
<h3>{{ solution.title }}</h3>
</div>
<ol v-if="solution.key !== 'personal'" class="solution-points">
<li v-for="point in solution.points" :key="point">{{ point }}</li>
</ol>
<p v-else class="solution-personal-text">{{ solution.points[0] }}</p>
<div class="solution-qr-row">
<span>具体咨询</span>
<QrPlaceholder :label="solution.qrLabel" :title="`${solution.tab}咨询二维码`" />
</div>
</article>
</div>
<div class="solution-panel solution-panel--mobile">
<article
id="solution-panel"
:key="activeSolution.key"

View File

@ -53,17 +53,17 @@ export const solutions = [
}
]
export const caseCategories = ['国家医学中心', '公立医院', '妇幼保健', '医疗集团客户']
export const caseCategories = ['国家医学中心', '公立医院', '妇幼保健客户', '医疗集团客户']
export const cases = [
{ short: '骨', name: '国家骨科医学中心\n上海市第六人民医院', tone: 'blue', category: '国家医学中心' },
{ short: '呼', name: '国家呼吸医学中心\n广州医科大学附属第一医院', tone: 'orange', category: '国家医学中心' },
{ short: '广', name: '广医口腔', tone: 'blue', category: '公立医院' },
{ short: '广', name: '广医口腔', tone: 'blue', category: '公立医院' },
{ short: '丹', name: '丹红互联网医院', tone: 'red', category: '公立医院' },
{ short: '十', name: '圣德', tone: 'green', category: '妇幼保健' },
{ short: '中', name: '中信医疗', tone: 'red', category: '医疗集团客户' },
{ short: 'Z', name: '浙江震元', tone: 'cyan', category: '医疗集团客户' },
{ short: '合', name: '合禾', tone: 'green', category: '医疗集团客户' },
{ short: '…', name: '更多案例\n……', tone: 'gray', category: '医疗集团客户' }
{ short: '骨', logo: '', name: '国家骨科医学中心\n上海市第六人民医院', tone: 'blue', category: '国家医学中心' },
{ short: '呼', logo: '', name: '国家呼吸医学中心\n广州医科大学附属第一医院', tone: 'orange', category: '国家医学中心' },
{ short: '广', logo: '', name: '广医口腔', tone: 'blue', category: '公立医院' },
{ short: '广', logo: '', name: '广医口腔', tone: 'blue', category: '公立医院' },
{ short: '丹', logo: '', name: '丹红互联网医院', tone: 'red', category: '公立医院' },
{ short: '十', logo: '', name: '圣德', tone: 'green', category: '妇幼保健客户' },
{ short: '中', logo: '', name: '中信医疗', tone: 'red', category: '医疗集团客户' },
{ short: 'Z', logo: '', name: '浙江震元', tone: 'cyan', category: '医疗集团客户' },
{ short: '合', logo: '', name: '合禾', tone: 'green', category: '医疗集团客户' },
{ short: '…', logo: '', name: '更多案例\n……', tone: 'gray', category: '医疗集团客户' }
]

View File

@ -130,14 +130,6 @@ button {
cursor: pointer;
}
.nav-toggle span {
display: block;
width: 20px;
height: 2px;
border-radius: 999px;
background: var(--brand);
}
.nav-link {
position: relative;
padding: 31px 0 28px;
@ -244,6 +236,10 @@ button {
color: #171b23;
}
.hero-title-nowrap {
white-space: nowrap;
}
.hero-badge {
display: inline-flex;
align-items: center;
@ -444,12 +440,12 @@ button {
align-items: center;
gap: 9px;
min-height: 36px;
padding: 8px 13px;
border-radius: 10px;
background: linear-gradient(180deg, #fff, #fff5ec);
padding: 8px 12px;
border-radius: 6px;
background: #fff7ec;
color: #535b68;
font-size: 14px;
box-shadow: inset 0 0 0 1px rgba(255, 138, 0, 0.10);
border: 1px solid rgba(255, 138, 0, 0.12);
}
.check-dot {
@ -471,9 +467,9 @@ button {
.solution-tabs {
position: relative;
display: grid;
display: none;
grid-template-columns: repeat(3, 1fr);
width: min(760px, 100%);
width: 100%;
min-height: 58px;
margin: 0 auto 28px;
padding: 6px;
@ -514,9 +510,13 @@ button {
.solution-grid {
display: grid;
grid-template-columns: 1fr;
width: min(760px, 100%);
margin: 0 auto;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
width: 100%;
}
.solution-panel {
display: none;
}
.solution-card {
@ -683,12 +683,26 @@ button {
transition: all 0.2s ease;
}
.case-logo {
.case-logo-slot {
flex: 0 0 auto;
width: 48px;
height: 48px;
display: grid;
place-items: center;
}
.case-logo-slot img {
max-width: 100%;
max-height: 100%;
display: block;
object-fit: contain;
}
.case-logo-fallback {
width: 48px;
height: 48px;
display: grid;
place-items: center;
border-radius: 14px;
color: #fff;
font-weight: 900;
@ -715,7 +729,7 @@ button {
}
.contact-card {
width: min(960px, 100%);
width: 100%;
margin: 0 auto;
padding: 34px 40px;
border-radius: 24px;
@ -830,6 +844,9 @@ button {
.feature-grid {
grid-template-columns: repeat(2, 1fr);
}
.solution-grid {
grid-template-columns: repeat(2, 1fr);
}
.case-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@ -901,7 +918,14 @@ button {
.contact-manager-grid {
grid-template-columns: 1fr;
}
.solution-grid--desktop {
display: none;
}
.solution-panel--mobile {
display: block;
}
.solution-tabs {
display: grid;
grid-template-columns: repeat(3, 1fr);
min-height: 50px;
padding: 5px;
@ -954,6 +978,7 @@ button {
}
.hero-copy h1 {
font-size: 31px;
letter-spacing: -0.05em;
}
.section-title {
gap: 12px;