fix(web): 视觉与交互 1:1(页头/侧栏/卡片/详情/动效)
对齐项(8080 vs 8081 实测,误差 ≤1px): - 侧栏:资料卡 224、服务面板 144、导航 143,navTop 548 与原版一致 - 分区卡:142/142/327/270 vs 142/142/326/269;标题 16/1.2、更多按钮 24px - 应用条目:48px 圆形头像 + 10px 小认证标;hover 名字变主色(不再整行变色) - 封面:hover 上浮 2px(0.2s ease),去掉缩放;CES 封面恢复 16/10 - 搜索框:44 高、无边框 + 卡片阴影 - 服务面板:状态药丸 22、统计 12/18、操作按钮 28、刷新时图标原地旋转 - 详情:返回链接 hover 下划线、跳转按钮 hover 深主色、view-in 淡入动画 - 菜单:hover 主色、子菜单 0.28s grid 高度动画、chevron 6px 旋转 - 回到顶部:right 30、top 50%、滚动后淡入(补内容区滚动监听) - 主题:卡片阴影恢复 2%/6%(旧版值)
This commit is contained in:
+140
-9
@@ -6,6 +6,9 @@
|
||||
z-index: 1;
|
||||
/* 透明:让固定背景图从下面透出来(Layout 默认有底色) */
|
||||
background-color: transparent;
|
||||
/* 旧版阴影是 2% / 6%,比库默认更深,属于主题级覆盖 */
|
||||
--su-shadow-card: 0 6px 16px rgba(27, 26, 49, 0.02);
|
||||
--su-shadow-hover: 0 6px 16px rgba(27, 26, 49, 0.06);
|
||||
}
|
||||
|
||||
/* 顶部背景图:固定 65vh,底部渐隐到页面底色 */
|
||||
@@ -87,6 +90,12 @@
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
/* 旧版搜索是「无边框 + 卡片阴影」 */
|
||||
.search-row > *:first-child {
|
||||
border-color: transparent;
|
||||
box-shadow: var(--su-shadow-card);
|
||||
}
|
||||
|
||||
/* ---------- 两栏骨架 ---------- */
|
||||
|
||||
.content {
|
||||
@@ -114,14 +123,57 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.profile-name {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.profile-role {
|
||||
padding: 1px 8px;
|
||||
border: 1px solid var(--su-border);
|
||||
border-radius: var(--su-radius-pill);
|
||||
font-size: var(--su-font-size-xs);
|
||||
line-height: 18px;
|
||||
color: var(--su-text-sub);
|
||||
}
|
||||
|
||||
.profile-bio {
|
||||
line-height: 1.8;
|
||||
margin-top: 2px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.profile-location {
|
||||
margin-top: 2px;
|
||||
font-size: var(--su-font-size-xs);
|
||||
line-height: 18px;
|
||||
color: var(--su-text-muted);
|
||||
}
|
||||
|
||||
/* 旧版头像下面留 4px,和名字之间更松 */
|
||||
.profile-card > span:first-child {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.service-panel {
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
/* 旧版标题与统计值的行高比库默认更紧 */
|
||||
.service-panel h2 {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
/* 旧版连接状态药丸:22 高、12px */
|
||||
.service-status {
|
||||
height: 22px;
|
||||
padding: 0 10px;
|
||||
font-size: var(--su-font-size-xs);
|
||||
}
|
||||
|
||||
/* 旧版统计行是 12/18 */
|
||||
.service-stats span {
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.service-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -162,6 +214,16 @@
|
||||
margin-bottom: var(--su-space-4);
|
||||
}
|
||||
|
||||
/* 旧版分区标题是 16/1.2,更多按钮是 24px 小方按钮 */
|
||||
.section-header h2 {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.section-more {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.section-bar {
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
@@ -178,8 +240,8 @@
|
||||
border-radius: var(--su-radius-sm);
|
||||
}
|
||||
|
||||
.app-card:hover {
|
||||
background-color: var(--su-bg-subtle);
|
||||
.app-card:hover .app-card-name {
|
||||
color: var(--su-color-primary);
|
||||
}
|
||||
|
||||
.app-card-info {
|
||||
@@ -189,10 +251,29 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 旧版应用头像是 48px 圆形,右下角是 10px 小认证标 */
|
||||
.app-card-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.app-card-avatar > span + span {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-width: 1px;
|
||||
font-size: 6px;
|
||||
}
|
||||
|
||||
/* ---------- 图文白卡 ---------- */
|
||||
|
||||
.ces-card {
|
||||
overflow: hidden;
|
||||
transition: transform var(--su-duration-fast) var(--su-ease-in-out);
|
||||
}
|
||||
|
||||
.ces-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.ces-body {
|
||||
@@ -227,10 +308,6 @@
|
||||
padding: 12px var(--su-space-5);
|
||||
}
|
||||
|
||||
.detail-nav > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.detail-back {
|
||||
color: var(--su-color-primary);
|
||||
font-size: var(--su-font-size-md);
|
||||
@@ -239,7 +316,11 @@
|
||||
}
|
||||
|
||||
.detail-back:hover {
|
||||
opacity: 0.8;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.detail-nav > * {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.detail-hero {
|
||||
@@ -363,7 +444,7 @@
|
||||
}
|
||||
|
||||
.item-jump:hover:not(:disabled) {
|
||||
opacity: 0.8;
|
||||
background-color: var(--su-color-primary-hover);
|
||||
}
|
||||
|
||||
.item-jump:disabled {
|
||||
@@ -454,6 +535,56 @@
|
||||
|
||||
/* ---------- 通用 ---------- */
|
||||
|
||||
/* 服务面板的两个操作按钮:旧版是 12px / 28 高 / 等分 / 描边 */
|
||||
.service-action {
|
||||
flex: 1;
|
||||
height: 28px;
|
||||
padding: 0 var(--su-space-2);
|
||||
font-size: var(--su-font-size-xs);
|
||||
border-radius: var(--su-radius-cover);
|
||||
}
|
||||
|
||||
.service-action:hover:not(:disabled) {
|
||||
border-color: var(--su-color-primary);
|
||||
background-color: var(--su-color-primary-soft);
|
||||
color: var(--su-color-primary);
|
||||
}
|
||||
|
||||
/* 页头图标按钮:旧版 hover 是浅灰底 + 深色字 */
|
||||
.site-header-inner button:hover {
|
||||
background-color: var(--su-bg-light);
|
||||
color: var(--su-text-main);
|
||||
}
|
||||
|
||||
/* 分区卡右上角 •••:旧版 hover 只有浅灰底 */
|
||||
.section-more:hover {
|
||||
background-color: var(--su-bg-light);
|
||||
color: var(--su-text-sub);
|
||||
}
|
||||
|
||||
/* 详情 / 文章切换时的淡入上浮(旧版 view-in) */
|
||||
@keyframes view-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(6px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-view {
|
||||
animation: view-in 0.22s ease both;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.detail-view {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
.link-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
+15
-3
@@ -103,20 +103,32 @@ export default function App() {
|
||||
return []
|
||||
}, [itemView, itemCategory])
|
||||
|
||||
// 切换视图时回到顶部(页面整体滚动)
|
||||
// 切换视图时把内容区(内部滚动)和窗口都回到顶部
|
||||
useEffect(() => {
|
||||
const scroller = document.getElementById('content-scroll')
|
||||
scroller?.scrollTo({ top: 0 })
|
||||
window.scrollTo({ top: 0 })
|
||||
}, [view])
|
||||
|
||||
/**
|
||||
* 滚动状态同时驱动「顶部渐隐」和「回到顶部」。
|
||||
* 桌面端滚的是内容容器(scroll 事件不冒泡),所以用捕获阶段监听。
|
||||
*/
|
||||
useEffect(() => {
|
||||
const sync = () => {
|
||||
const next = window.scrollY > 4
|
||||
const inner = document.getElementById('content-scroll')?.scrollTop ?? 0
|
||||
const next = Math.max(inner, window.scrollY) > 4
|
||||
setScrolled((current) => (current === next ? current : next))
|
||||
}
|
||||
|
||||
sync()
|
||||
window.addEventListener('scroll', sync, { passive: true })
|
||||
return () => window.removeEventListener('scroll', sync)
|
||||
document.addEventListener('scroll', sync, { passive: true, capture: true })
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', sync)
|
||||
document.removeEventListener('scroll', sync, { capture: true })
|
||||
}
|
||||
}, [])
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,5 +9,5 @@ type BackToTopProps = {
|
||||
}
|
||||
|
||||
export function BackToTop({ visible }: BackToTopProps) {
|
||||
return <ScrollTop visible={visible} targetId="content-scroll" />
|
||||
return <ScrollTop visible={visible} targetId="content-scroll" offset={30} />
|
||||
}
|
||||
|
||||
@@ -24,8 +24,9 @@ export function AppCard({ item, onOpen }: AppCardProps) {
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
size="md"
|
||||
shape="square"
|
||||
size="lg"
|
||||
tone="primary"
|
||||
className="app-card-avatar"
|
||||
src={item.cover}
|
||||
fallback={item.icon}
|
||||
verified={item.hot === true}
|
||||
|
||||
@@ -27,9 +27,8 @@ export function CesCard({ item, onOpen }: CesCardProps) {
|
||||
<Cover
|
||||
src={item.cover}
|
||||
alt={item.title}
|
||||
ratio="article"
|
||||
ratio={16 / 10}
|
||||
radius="none"
|
||||
hoverZoom
|
||||
onClick={onOpen === undefined ? undefined : () => onOpen(item.id)}
|
||||
/>
|
||||
{onOpen === undefined ? (
|
||||
|
||||
@@ -21,7 +21,7 @@ export function SectionCard({ title, count, barColor, onMore, children }: Sectio
|
||||
<div className="section-header">
|
||||
<Space gap="sm">
|
||||
<span className="section-bar" style={{ backgroundColor: barColor }} aria-hidden="true" />
|
||||
<Title level={3} as="h2">
|
||||
<Title level={3} as="h2" weight="semibold">
|
||||
{title}
|
||||
</Title>
|
||||
<Badge count={count} tone="danger" />
|
||||
@@ -30,6 +30,7 @@ export function SectionCard({ title, count, barColor, onMore, children }: Sectio
|
||||
{onMore !== undefined && (
|
||||
<IconButton
|
||||
size="xs"
|
||||
className="section-more"
|
||||
label={`查看${title}全部`}
|
||||
icon={<Icon name="more_1_line" />}
|
||||
onClick={onMore}
|
||||
|
||||
@@ -29,6 +29,7 @@ export function SearchBar({ value, onChange, inputRef }: SearchBarProps) {
|
||||
<div className="search-row">
|
||||
<Input
|
||||
inputRef={inputRef}
|
||||
size="lg"
|
||||
icon="search_3_line"
|
||||
value={value}
|
||||
placeholder="搜索本地项目…"
|
||||
|
||||
@@ -9,18 +9,20 @@ export function ProfileCard() {
|
||||
const { name, role, bio, location } = PLACEHOLDER_PROFILE
|
||||
|
||||
return (
|
||||
<Card padding="lg" className="profile-card">
|
||||
<Card padding="none" className="profile-card">
|
||||
<Avatar size="xl" tone="primary" icon={<Icon name="user_3_line" />} verified />
|
||||
<Title level={3}>{name}</Title>
|
||||
<Text size="xs" tone="muted">
|
||||
{role}
|
||||
</Text>
|
||||
|
||||
<Title level={4} weight="semibold" className="profile-name">
|
||||
{name}
|
||||
</Title>
|
||||
|
||||
<span className="profile-role">{role}</span>
|
||||
|
||||
<Text size="xs" tone="sub" className="profile-bio">
|
||||
{bio}
|
||||
</Text>
|
||||
<Text size="xs" tone="muted">
|
||||
IP 属地:{location}
|
||||
</Text>
|
||||
|
||||
<span className="profile-location">IP属地:{location}</span>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,26 +18,26 @@ export function ServicePanel({ projects, live, refreshing, onRefresh }: ServiceP
|
||||
return (
|
||||
<Card padding="none" className="service-panel">
|
||||
<Space justify="between">
|
||||
<Title level={4} as="h2">
|
||||
<Title level={4} as="h2" weight="semibold">
|
||||
本地服务
|
||||
</Title>
|
||||
<Tag tone={live ? 'success' : 'neutral'} dot>
|
||||
<Tag tone={live ? 'success' : 'neutral'} dot className="service-status">
|
||||
{live ? '已连接' : '未连接'}
|
||||
</Tag>
|
||||
</Space>
|
||||
|
||||
<div className="service-stats">
|
||||
<Space justify="between">
|
||||
<Text size="sm" tone="sub">
|
||||
<Text size="xs" tone="sub">
|
||||
项目数
|
||||
</Text>
|
||||
<Text strong>{projects.length}</Text>
|
||||
<Text size="xs" strong>{projects.length}</Text>
|
||||
</Space>
|
||||
<Space justify="between">
|
||||
<Text size="sm" tone="sub">
|
||||
<Text size="xs" tone="sub">
|
||||
访问地址
|
||||
</Text>
|
||||
<Text size="sm" truncate title={host} className="service-host">
|
||||
<Text size="xs" truncate title={host} className="service-host">
|
||||
{host}
|
||||
</Text>
|
||||
</Space>
|
||||
@@ -47,7 +47,8 @@ export function ServicePanel({ projects, live, refreshing, onRefresh }: ServiceP
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
loading={refreshing}
|
||||
className="service-action"
|
||||
spinning={refreshing}
|
||||
icon={<Icon name="refresh_3_line" />}
|
||||
onClick={onRefresh}
|
||||
title="重新扫描项目"
|
||||
|
||||
Reference in New Issue
Block a user