补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
887 行
22 KiB
Plaintext
887 行
22 KiB
Plaintext
<template>
|
||
<view class="article-page" :class="pageRtlClass" :style="pageTheme">
|
||
<!-- 顶部局部极光微光晕 (Ambient Aurora Glow, 严格受控 < 8% 面积) -->
|
||
<view class="ambient-aurora-glow"></view>
|
||
|
||
<!-- 顶部精细阅读进度指示条 (Top Reading Progress Bar) -->
|
||
<view class="reading-progress-track">
|
||
<view
|
||
class="reading-progress-fill"
|
||
:style="{ width: readingProgressPercent + '%' }"
|
||
></view>
|
||
</view>
|
||
|
||
<!-- 顶部轻量导航条 (Top Navigation Bar) -->
|
||
<view class="article-nav-bar">
|
||
<view class="nav-back-capsule" @click="handleBack">
|
||
<text class="nav-back-arrow">{{ isRtlMode ? '→' : '←' }}</text>
|
||
<text class="nav-back-text">{{ backText }}</text>
|
||
</view>
|
||
<view v-if="!state.loading && !state.empty" class="nav-meta-badge">
|
||
<text class="nav-meta-text">{{ readTimeBadge }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 骨架加载态 -->
|
||
<view v-if="state.loading" class="article-skeleton">
|
||
<view class="skeleton-cover"></view>
|
||
<view class="skeleton-pill-row">
|
||
<view class="skeleton-pill"></view>
|
||
<view class="skeleton-pill pill-short"></view>
|
||
</view>
|
||
<view class="skeleton-line line-title"></view>
|
||
<view class="skeleton-bento-summary"></view>
|
||
<view class="skeleton-line"></view>
|
||
<view class="skeleton-line"></view>
|
||
<view class="skeleton-line line-short"></view>
|
||
</view>
|
||
|
||
<!-- 空状态 / 异常卡片 -->
|
||
<view v-else-if="state.empty" class="article-empty">
|
||
<view class="article-empty-card">
|
||
<view class="empty-icon-box">
|
||
<text class="empty-icon-symbol">!</text>
|
||
</view>
|
||
<text class="article-empty-title">{{ emptyTitle }}</text>
|
||
<text class="article-empty-desc">{{ emptyHint }}</text>
|
||
<!-- 空态返回为描边次按钮,避免与内容态底栏实心主 CTA 并存第二颗实心主按钮 -->
|
||
<button class="empty-back-btn article-empty-cta" @click="handleBack">
|
||
<text class="empty-back-btn-text">{{ backText }}</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 文章主体内容滚动容器 -->
|
||
<scroll-view
|
||
v-else
|
||
class="article-scroll"
|
||
:scroll-y="true"
|
||
@scroll="handleScroll"
|
||
>
|
||
<view class="article-content">
|
||
<!-- 1. 文章媒体封面大图 (若配置) -->
|
||
<view v-if="state.mediaUrl !== ''" class="cover-wrapper">
|
||
<image
|
||
class="article-cover"
|
||
:src="state.mediaUrl"
|
||
mode="aspectFill"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 2. 出版级排版头部 (Kicker 胶囊 + 阅读时间 + 24px 700 主标题) -->
|
||
<view class="article-header">
|
||
<view class="header-badge-row">
|
||
<view class="kicker-badge">
|
||
<view class="kicker-pulse-dot"></view>
|
||
<text class="kicker-text">{{ kickerText }}</text>
|
||
</view>
|
||
<view class="reading-time-pill">
|
||
<text class="reading-time-text">{{ readTimeBadge }}</text>
|
||
</view>
|
||
</view>
|
||
<text class="article-title">{{ state.title }}</text>
|
||
</view>
|
||
|
||
<!-- 3. 核心导读 Bento 卡片 (Executive Summary Bento Card) -->
|
||
<view v-if="state.summaryText !== ''" class="summary-bento-card">
|
||
<view class="summary-bento-bar"></view>
|
||
<view class="summary-bento-content">
|
||
<view class="summary-heading-row">
|
||
<view class="summary-heading-dot"></view>
|
||
<text class="summary-heading-label">{{ summaryHeadingText }}</text>
|
||
</view>
|
||
<text class="summary-body-text">{{ state.summaryText }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 4. 正文排版区 (Golden Reading Measure 45-75 字符, max-width: 720px, 行高 1.65) -->
|
||
<view class="article-body-wrapper">
|
||
<rich-text
|
||
v-if="state.isHtmlContent"
|
||
class="article-rich"
|
||
:nodes="state.content"
|
||
></rich-text>
|
||
<text v-else class="article-body">{{ state.content }}</text>
|
||
</view>
|
||
|
||
<!-- 5. 专栏主理人微展台 Bento 卡片 (Author Profile Micro Showcase) -->
|
||
<view v-if="hasAuthor" class="author-showcase-card" @click="handleViewAuthor">
|
||
<view class="author-card-content">
|
||
<view class="author-avatar-wrap">
|
||
<image
|
||
class="author-avatar"
|
||
:src="authorAvatarUrl"
|
||
mode="aspectFill"
|
||
/>
|
||
<view class="author-status-ring"></view>
|
||
</view>
|
||
<view class="author-info-column">
|
||
<view class="author-tag-row">
|
||
<text class="author-tag-text">{{ authorTagText }}</text>
|
||
</view>
|
||
<text class="author-name">{{ state.authorName }}</text>
|
||
<text v-if="authorSubtitle !== ''" class="author-subtitle">{{ authorSubtitle }}</text>
|
||
</view>
|
||
<view class="author-action-chip">
|
||
<text class="author-action-label">{{ viewPortalText }}</text>
|
||
<text class="author-action-arrow">↗</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部安全垫高,严防遮挡底栏主 CTA 与避让视口安全区 -->
|
||
<view class="article-bottom-spacer"></view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 6. 底部悬浮主 CTA 栏 (一屏唯一定位主 CTA:复制分享链接) -->
|
||
<view v-if="!state.loading && !state.empty" class="article-cta-bar">
|
||
<button class="primary-cta-btn" @click="handleCopy">
|
||
<text class="primary-cta-btn-text">{{ shareCtaText }}</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { utsLightColors, utsRadius, utsSpacing, utsTypography } from '../../common/tokens.uts';
|
||
import { getCurrentLocale, isRtl } from '../../common/locale.uts';
|
||
import {
|
||
createArticleDetailState,
|
||
applyArticleDetailOptions,
|
||
loadArticleDetail,
|
||
performArticleShareAction,
|
||
goBackFromArticle,
|
||
resolveArticleDetailText,
|
||
getCurrentPageUrl,
|
||
updateArticleScrollDepth,
|
||
reportArticleStay,
|
||
navToAuthorPortal,
|
||
ArticleDetailPageOptions,
|
||
} from './useArticleDetail.uts';
|
||
|
||
type ScrollDetail = {
|
||
scrollTop: number;
|
||
scrollHeight: number;
|
||
};
|
||
|
||
type ScrollCustomEvent = {
|
||
detail: ScrollDetail;
|
||
};
|
||
|
||
const defaultAvatar = 'https://cdn.omnicard.ai/assets/default-avatar.png';
|
||
|
||
const pageTheme: Record<string, string> = {
|
||
'--oc-primary': utsLightColors.primary,
|
||
'--oc-primary-gradient': utsLightColors.primaryGradient,
|
||
'--oc-primary-soft': utsLightColors.primarySoft,
|
||
'--oc-aurora': utsLightColors.aurora,
|
||
'--oc-ai': '#7C5CFF',
|
||
'--oc-bg': utsLightColors.bg,
|
||
'--oc-surface': utsLightColors.surface,
|
||
'--oc-border': utsLightColors.border,
|
||
'--oc-text': utsLightColors.text,
|
||
'--oc-text-2': utsLightColors.text2,
|
||
'--oc-success': utsLightColors.success,
|
||
'--oc-warning': utsLightColors.warning,
|
||
'--oc-error': utsLightColors.error,
|
||
'--oc-radius-btn': utsRadius.button,
|
||
'--oc-radius-card': utsRadius.card,
|
||
'--oc-radius-modal': utsRadius.modal,
|
||
'--oc-radius-pill': utsRadius.pill,
|
||
'--oc-shadow-card': utsTypography.shadowCard,
|
||
'--oc-shadow-cta': utsTypography.shadowCta,
|
||
'--oc-shadow-float': utsTypography.shadowFloat,
|
||
'--oc-shadow': utsTypography.shadow,
|
||
'--oc-space-4': utsSpacing.space4,
|
||
'--oc-space-8': utsSpacing.space8,
|
||
'--oc-space-12': utsSpacing.space12,
|
||
'--oc-space-16': utsSpacing.space16,
|
||
'--oc-space-20': utsSpacing.space20,
|
||
'--oc-space-24': utsSpacing.space24,
|
||
'--oc-space-32': utsSpacing.space32,
|
||
};
|
||
|
||
const state = reactive(createArticleDetailState());
|
||
const readingProgressPercent = ref(0);
|
||
|
||
const isRtlMode = computed((): boolean => {
|
||
return isRtl(getCurrentLocale());
|
||
});
|
||
|
||
const pageRtlClass = computed((): string => {
|
||
return isRtlMode.value ? 'article-rtl' : '';
|
||
});
|
||
|
||
const kickerText = computed((): string => {
|
||
return resolveArticleDetailText('kicker');
|
||
});
|
||
|
||
const summaryHeadingText = computed((): string => {
|
||
return resolveArticleDetailText('summary');
|
||
});
|
||
|
||
const authorTagText = computed((): string => {
|
||
return resolveArticleDetailText('author');
|
||
});
|
||
|
||
const viewPortalText = computed((): string => {
|
||
return resolveArticleDetailText('viewPortal');
|
||
});
|
||
|
||
const shareCtaText = computed((): string => {
|
||
return resolveArticleDetailText('shareCta');
|
||
});
|
||
|
||
const backText = computed((): string => {
|
||
return resolveArticleDetailText('back');
|
||
});
|
||
|
||
const emptyTitle = computed((): string => {
|
||
return resolveArticleDetailText('empty');
|
||
});
|
||
|
||
const emptyHint = computed((): string => {
|
||
return resolveArticleDetailText('emptyHint');
|
||
});
|
||
|
||
const readTimeBadge = computed((): string => {
|
||
const mins = state.readTimeMinutes > 0 ? state.readTimeMinutes : 1;
|
||
const label = resolveArticleDetailText('readTime');
|
||
const unit = resolveArticleDetailText('minRead');
|
||
return `${label} ${mins} ${unit}`;
|
||
});
|
||
|
||
const hasAuthor = computed((): boolean => {
|
||
return state.authorName !== '';
|
||
});
|
||
|
||
const authorAvatarUrl = computed((): string => {
|
||
return state.authorAvatar !== '' ? state.authorAvatar : defaultAvatar;
|
||
});
|
||
|
||
const authorSubtitle = computed((): string => {
|
||
if (state.authorTitle !== '' && state.authorCompany !== '') {
|
||
return `${state.authorTitle} · ${state.authorCompany}`;
|
||
}
|
||
return state.authorTitle !== '' ? state.authorTitle : state.authorCompany;
|
||
});
|
||
|
||
function handleScroll(e: ScrollCustomEvent): void {
|
||
if (e != null && e.detail != null) {
|
||
const scrollTop = e.detail.scrollTop;
|
||
const scrollHeight = e.detail.scrollHeight;
|
||
const winHeight = 667;
|
||
const depth = updateArticleScrollDepth(state, scrollTop, scrollHeight, winHeight);
|
||
readingProgressPercent.value = depth;
|
||
}
|
||
}
|
||
|
||
async function handleCopy(): Promise<void> {
|
||
// 有系统分享 API 时走 shareArticleViaSystem(仅 success 提示 shareSuccess);
|
||
// 否则 P22 复制链接(成功提示直链已复制)。失败/取消不提示 shareSuccess。
|
||
await performArticleShareAction(state, getCurrentPageUrl(state));
|
||
}
|
||
|
||
function handleBack(): void {
|
||
goBackFromArticle(state);
|
||
}
|
||
|
||
function handleViewAuthor(): void {
|
||
navToAuthorPortal(state);
|
||
}
|
||
|
||
onLoad((options: ArticleDetailPageOptions) => {
|
||
const ready = applyArticleDetailOptions(state, options);
|
||
if (ready) {
|
||
loadArticleDetail(state);
|
||
}
|
||
});
|
||
|
||
onUnload(() => {
|
||
reportArticleStay(state, state.scrollDepth);
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.article-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
position: relative;
|
||
background-color: var(--oc-bg, #F5F8FC);
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.article-rtl {
|
||
direction: rtl;
|
||
}
|
||
|
||
/* 顶部极光弥散微光晕 (受控局部区域,面积 < 8%) */
|
||
.ambient-aurora-glow {
|
||
position: absolute;
|
||
top: -30px;
|
||
left: 15%;
|
||
right: 15%;
|
||
height: 100px;
|
||
background: var(--oc-aurora, linear-gradient(135deg, #00D2FF 0%, #0088FF 50%, #6366F1 100%));
|
||
opacity: 0.07;
|
||
filter: blur(40px);
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
}
|
||
|
||
/* 顶部精细阅读进度条 */
|
||
.reading-progress-track {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 3px;
|
||
background-color: transparent;
|
||
z-index: 120;
|
||
}
|
||
|
||
.reading-progress-fill {
|
||
height: 100%;
|
||
background: var(--oc-primary-gradient, linear-gradient(135deg, #00A3FF 0%, #0B63E5 100%));
|
||
transition: width 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||
}
|
||
|
||
/* 顶部导航条 */
|
||
.article-nav-bar {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding-inline: var(--oc-space-16, 16px);
|
||
padding-block: var(--oc-space-8, 8px);
|
||
z-index: 10;
|
||
}
|
||
|
||
.nav-back-capsule {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
min-height: 44px;
|
||
min-width: 44px;
|
||
padding-inline: var(--oc-space-12, 12px);
|
||
border-radius: var(--oc-radius-pill, 9999px);
|
||
background-color: var(--oc-surface, #FFFFFF);
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
box-shadow: 0 2px 6px rgba(16, 24, 40, 0.04);
|
||
}
|
||
|
||
.nav-back-capsule:active {
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.nav-back-arrow {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--oc-text, #101828);
|
||
margin-inline-end: var(--oc-space-4, 4px);
|
||
}
|
||
|
||
.nav-back-text {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--oc-text, #101828);
|
||
}
|
||
|
||
.nav-meta-badge {
|
||
display: flex;
|
||
align-items: center;
|
||
padding-inline: var(--oc-space-12, 12px);
|
||
padding-block: 6px;
|
||
border-radius: var(--oc-radius-pill, 9999px);
|
||
background-color: rgba(255, 255, 255, 0.8);
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
}
|
||
|
||
.nav-meta-text {
|
||
font-size: 12px;
|
||
color: var(--oc-text-2, #64748B);
|
||
line-height: 1.25;
|
||
}
|
||
|
||
/* 骨架屏态 */
|
||
.article-skeleton {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-inline: var(--oc-space-16, 16px);
|
||
padding-block: var(--oc-space-16, 16px);
|
||
max-width: 720px;
|
||
width: 100%;
|
||
margin-inline: auto;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.skeleton-cover {
|
||
height: 200px;
|
||
border-radius: var(--oc-radius-card, 20px);
|
||
background-color: var(--oc-border, #EBF1F7);
|
||
margin-block-end: var(--oc-space-20, 20px);
|
||
}
|
||
|
||
.skeleton-pill-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
gap: var(--oc-space-8, 8px);
|
||
margin-block-end: var(--oc-space-12, 12px);
|
||
}
|
||
|
||
.skeleton-pill {
|
||
height: 22px;
|
||
width: 80px;
|
||
border-radius: var(--oc-radius-pill, 9999px);
|
||
background-color: var(--oc-border, #EBF1F7);
|
||
}
|
||
|
||
.pill-short {
|
||
width: 100px;
|
||
}
|
||
|
||
.skeleton-line {
|
||
height: 14px;
|
||
border-radius: 8px;
|
||
background-color: var(--oc-border, #EBF1F7);
|
||
margin-block-end: var(--oc-space-12, 12px);
|
||
}
|
||
|
||
.line-title {
|
||
height: 28px;
|
||
width: 75%;
|
||
margin-block-end: var(--oc-space-20, 20px);
|
||
}
|
||
|
||
.line-short {
|
||
width: 45%;
|
||
}
|
||
|
||
.skeleton-bento-summary {
|
||
height: 80px;
|
||
border-radius: var(--oc-radius-card, 20px);
|
||
background-color: var(--oc-border, #EBF1F7);
|
||
margin-block-end: var(--oc-space-20, 20px);
|
||
}
|
||
|
||
/* 空状态卡片 */
|
||
.article-empty {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex: 1;
|
||
padding-inline: var(--oc-space-16, 16px);
|
||
padding-block: var(--oc-space-24, 24px);
|
||
}
|
||
|
||
.article-empty-card {
|
||
width: 100%;
|
||
max-width: 360px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: var(--oc-space-24, 24px);
|
||
background-color: var(--oc-surface, #FFFFFF);
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
border-radius: var(--oc-radius-card, 20px);
|
||
box-shadow: var(--oc-shadow-card, 0 8px 30px rgba(0, 0, 0, 0.04));
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.empty-icon-box {
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: 50%;
|
||
background-color: var(--oc-primary-soft, #EBF5FF);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-block-end: var(--oc-space-12, 12px);
|
||
}
|
||
|
||
.empty-icon-symbol {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
color: var(--oc-primary, #0088FF);
|
||
}
|
||
|
||
.article-empty-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--oc-text, #101828);
|
||
line-height: 1.4;
|
||
text-align: center;
|
||
margin-block-end: var(--oc-space-8, 8px);
|
||
}
|
||
|
||
.article-empty-desc {
|
||
font-size: 14px;
|
||
color: var(--oc-text-2, #64748B);
|
||
line-height: 1.6;
|
||
text-align: center;
|
||
margin-block-end: var(--oc-space-20, 20px);
|
||
}
|
||
|
||
.article-empty-cta {
|
||
max-width: 200px;
|
||
}
|
||
|
||
.empty-back-btn {
|
||
height: 44px;
|
||
min-height: 44px;
|
||
width: 100%;
|
||
max-width: 200px;
|
||
border-width: 1px;
|
||
border-style: solid;
|
||
border-color: var(--oc-border, #EBF1F7);
|
||
border-radius: var(--oc-radius-btn, 14px);
|
||
background-color: var(--oc-surface, #FFFFFF);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.empty-back-btn:active {
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.empty-back-btn-text {
|
||
color: var(--oc-text, #101828);
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
line-height: 1;
|
||
}
|
||
|
||
/* 滚动容器 */
|
||
.article-scroll {
|
||
flex: 1;
|
||
}
|
||
|
||
.article-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-inline: var(--oc-space-16, 16px);
|
||
padding-block-start: var(--oc-space-8, 8px);
|
||
max-width: 720px;
|
||
width: 100%;
|
||
margin-inline: auto;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* 封面大图 */
|
||
.cover-wrapper {
|
||
width: 100%;
|
||
height: 200px;
|
||
border-radius: var(--oc-radius-card, 20px);
|
||
overflow: hidden;
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
box-shadow: var(--oc-shadow-card, 0 8px 30px rgba(0, 0, 0, 0.04));
|
||
margin-block-end: var(--oc-space-20, 20px);
|
||
}
|
||
|
||
.article-cover {
|
||
width: 100%;
|
||
height: 200px;
|
||
}
|
||
|
||
/* 头部信息 */
|
||
.article-header {
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin-block-end: var(--oc-space-16, 16px);
|
||
}
|
||
|
||
.header-badge-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: var(--oc-space-8, 8px);
|
||
margin-block-end: var(--oc-space-12, 12px);
|
||
}
|
||
|
||
.kicker-badge {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding-inline: var(--oc-space-12, 12px);
|
||
padding-block: 4px;
|
||
border-radius: var(--oc-radius-pill, 9999px);
|
||
background-color: var(--oc-primary-soft, #EBF5FF);
|
||
border: 1px solid rgba(0, 136, 255, 0.15);
|
||
}
|
||
|
||
.kicker-pulse-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background-color: var(--oc-primary, #0088FF);
|
||
margin-inline-end: var(--oc-space-8, 8px);
|
||
}
|
||
|
||
.kicker-text {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--oc-primary, #0088FF);
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.reading-time-pill {
|
||
display: flex;
|
||
align-items: center;
|
||
padding-inline: var(--oc-space-8, 8px);
|
||
padding-block: 4px;
|
||
}
|
||
|
||
.reading-time-text {
|
||
font-size: 12px;
|
||
color: var(--oc-text-2, #64748B);
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.article-title {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: var(--oc-text, #101828);
|
||
line-height: 1.25;
|
||
}
|
||
|
||
/* 核心导读 Bento 卡片 (Executive Summary Bento Card) */
|
||
.summary-bento-card {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: row;
|
||
background-color: var(--oc-surface, #FFFFFF);
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
border-radius: var(--oc-radius-card, 20px);
|
||
box-shadow: var(--oc-shadow-card, 0 8px 30px rgba(0, 0, 0, 0.04));
|
||
overflow: hidden;
|
||
margin-block-end: var(--oc-space-20, 20px);
|
||
}
|
||
|
||
.summary-bento-bar {
|
||
width: 4px;
|
||
background: var(--oc-primary-gradient, linear-gradient(135deg, #00A3FF 0%, #0B63E5 100%));
|
||
}
|
||
|
||
.summary-bento-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: var(--oc-space-16, 16px);
|
||
}
|
||
|
||
.summary-heading-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-block-end: var(--oc-space-8, 8px);
|
||
}
|
||
|
||
.summary-heading-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background-color: var(--oc-primary, #0088FF);
|
||
margin-inline-end: var(--oc-space-8, 8px);
|
||
}
|
||
|
||
.summary-heading-label {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--oc-text, #101828);
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.summary-body-text {
|
||
font-size: 14px;
|
||
color: var(--oc-text-2, #64748B);
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* 正文排版区 */
|
||
.article-body-wrapper {
|
||
margin-block-end: var(--oc-space-24, 24px);
|
||
}
|
||
|
||
.article-rich {
|
||
font-size: 16px;
|
||
color: var(--oc-text, #101828);
|
||
line-height: 1.65;
|
||
}
|
||
|
||
.article-body {
|
||
font-size: 16px;
|
||
color: var(--oc-text, #101828);
|
||
line-height: 1.65;
|
||
}
|
||
|
||
/* 专栏作者微展台 Bento 卡片 (Author Profile Micro Showcase) */
|
||
.author-showcase-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
background-color: var(--oc-surface, #FFFFFF);
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
border-radius: var(--oc-radius-card, 20px);
|
||
box-shadow: var(--oc-shadow-card, 0 8px 30px rgba(0, 0, 0, 0.04));
|
||
padding: var(--oc-space-16, 16px);
|
||
margin-block-end: var(--oc-space-24, 24px);
|
||
transition: opacity 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||
}
|
||
|
||
.author-showcase-card:active {
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.author-card-content {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.author-avatar-wrap {
|
||
position: relative;
|
||
width: 52px;
|
||
height: 52px;
|
||
margin-inline-end: var(--oc-space-12, 12px);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.author-avatar {
|
||
width: 52px;
|
||
height: 52px;
|
||
border-radius: 50%;
|
||
border: 2px solid #FFFFFF;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
.author-status-ring {
|
||
position: absolute;
|
||
bottom: 0;
|
||
inset-inline-end: 0;
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background-color: var(--oc-success, #12B76A);
|
||
border: 2px solid #FFFFFF;
|
||
}
|
||
|
||
.author-info-column {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-width: 0;
|
||
}
|
||
|
||
.author-tag-row {
|
||
margin-block-end: 2px;
|
||
}
|
||
|
||
.author-tag-text {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: var(--oc-primary, #0088FF);
|
||
}
|
||
|
||
.author-name {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: var(--oc-text, #101828);
|
||
line-height: 1.3;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.author-subtitle {
|
||
font-size: 12px;
|
||
color: var(--oc-text-2, #64748B);
|
||
line-height: 1.4;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
margin-block-start: 2px;
|
||
}
|
||
|
||
.author-action-chip {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
min-height: 44px;
|
||
padding-inline: var(--oc-space-8, 8px);
|
||
margin-inline-start: var(--oc-space-8, 8px);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.author-action-label {
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
color: var(--oc-primary, #0088FF);
|
||
margin-inline-end: 2px;
|
||
}
|
||
|
||
.author-action-arrow {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
color: var(--oc-primary, #0088FF);
|
||
}
|
||
|
||
/* 底部常驻主 CTA 避让高度 */
|
||
.article-bottom-spacer {
|
||
height: calc(96px + env(safe-area-inset-bottom, 0px) + var(--window-bottom, 0px));
|
||
}
|
||
|
||
/* 底部常驻悬浮主 CTA 栏 */
|
||
.article-cta-bar {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: var(--window-bottom, 0px);
|
||
padding-block-start: 12px;
|
||
padding-inline: 16px;
|
||
padding-block-end: calc(16px + env(safe-area-inset-bottom, 0px));
|
||
background-color: rgba(255, 255, 255, 0.95);
|
||
backdrop-filter: blur(16px);
|
||
border-top: 1px solid var(--oc-border, #EBF1F7);
|
||
box-sizing: border-box;
|
||
z-index: 100;
|
||
}
|
||
|
||
/* 单屏唯一定位主 CTA 胶囊 */
|
||
.primary-cta-btn {
|
||
height: 48px;
|
||
min-height: 44px;
|
||
width: 100%;
|
||
border-width: 0;
|
||
border-radius: var(--oc-radius-btn, 14px);
|
||
background: var(--oc-primary-gradient, linear-gradient(135deg, #00A3FF 0%, #0B63E5 100%));
|
||
box-shadow: var(--oc-shadow-cta, 0 8px 20px rgba(0, 136, 255, 0.22));
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
transition: opacity 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||
}
|
||
|
||
.primary-cta-btn:active {
|
||
opacity: 0.88;
|
||
}
|
||
|
||
.primary-cta-btn-text {
|
||
color: #FFFFFF;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
line-height: 1;
|
||
}
|
||
|
||
/* 无障碍减弱动效 */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
* {
|
||
animation: none !important;
|
||
transition: none !important;
|
||
}
|
||
}
|
||
</style>
|