- 模块一: 出海案例作品集、博客动态发布、人名发音语音条、RTL阿语多语言切换 - 模块二: AI顾问人设配置、行业术语库润色、RAG切片沙盒调试、流式会话与意向留资卡片 - 模块三: 全球访客雷达大屏、多维加权意向算分、外部CRM对接推送、团队公海池流转 - 模块四: 移动端NFC安全挂失、NFC动态路由规则配置、动态防伪验签、展会制卡Zip批量导出 - 模块五: 组织架构与名片分配、移动端个人中心设置、离职交接与历史线索迁移、安全审计日志 - 模块六: Stripe国际信用卡订阅Webhook、独立二级域名与SSL检测、坐席配额预警、多币种发票 - 模块七: 纸质名片OCR解析入库、国际商务日程RFC 5545 ICS日历、微官网离线SWR预加载、展会实时排行榜大屏 - 模块八: 全球出海SEO OpenGraph元标签注入与社交爬虫探测、30点端到端全链路业务流集成测试套件
771 行
19 KiB
Plaintext
771 行
19 KiB
Plaintext
<template>
|
|
<view class="portal-page" :class="pageRtlClass">
|
|
<view class="locale-bar">
|
|
<view class="locale-selector-container">
|
|
<i18n-selector :model-value="currentLang" @change="switchLang" />
|
|
</view>
|
|
<text class="locale-chip locale-exchange" @click="openExchange">{{ textOf('card.exchange') }}</text>
|
|
</view>
|
|
|
|
<scroll-view class="portal-scroll" :scroll-y="true">
|
|
<!-- 骨架加载态 -->
|
|
<view v-if="state.loading" class="portal-skeleton">
|
|
<view class="skeleton-avatar"></view>
|
|
<view class="skeleton-line line-title"></view>
|
|
<view class="skeleton-line line-subtitle"></view>
|
|
<view class="skeleton-card"></view>
|
|
</view>
|
|
|
|
<!-- 异常提示态 -->
|
|
<view v-else-if="state.errorMessage" class="portal-error-card">
|
|
<text class="error-title">{{ textOf('portal.unavailable') }}</text>
|
|
<text class="error-desc">{{ state.errorMessage }}</text>
|
|
<button class="retry-btn" @click="reloadPortal">
|
|
<text class="retry-btn-text">{{ textOf('portal.retry') }}</text>
|
|
</button>
|
|
</view>
|
|
|
|
<!-- 核心微官网内容区 -->
|
|
<view v-else-if="state.card != null" class="portal-content">
|
|
<!-- 品牌标识 (若配置) -->
|
|
<view v-if="state.card!.brandKit && state.card!.brandKit!.logoUrl" class="brand-bar">
|
|
<image class="brand-logo" :src="state.card!.brandKit!.logoUrl" mode="heightFix" />
|
|
</view>
|
|
|
|
<!-- 1. Hero 身份区 -->
|
|
<view class="hero-card">
|
|
<view class="hero-top-row">
|
|
<image
|
|
class="hero-avatar"
|
|
:src="state.card!.avatarUrl || defaultAvatar"
|
|
mode="aspectFill"
|
|
/>
|
|
<view v-if="state.card!.timezone" class="hero-timezone-pill">
|
|
<text class="hero-timezone-text">{{ state.card!.timezone }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 姓名与发音试听 -->
|
|
<view class="hero-name-row">
|
|
<text class="hero-name">{{ state.card!.name }}</text>
|
|
<audio-pronounce
|
|
v-if="cardAudioUrl !== ''"
|
|
:audio-url="cardAudioUrl"
|
|
:label="textOf('portal.pronounce')"
|
|
/>
|
|
</view>
|
|
|
|
<!-- 头衔与所属公司 -->
|
|
<text class="hero-title">{{ state.card!.title }}</text>
|
|
<view class="hero-company-row" @click="handleCompanyClick">
|
|
<text class="hero-company-name">{{ state.card!.company }}</text>
|
|
<text class="hero-company-arrow">↗</text>
|
|
</view>
|
|
|
|
<!-- 个人签名 Slogan -->
|
|
<text v-if="state.card!.slogan" class="hero-slogan">{{ state.card!.slogan }}</text>
|
|
</view>
|
|
|
|
<!-- 2. 快捷动作条 (触控区 >= 44x44) -->
|
|
<view class="quick-action-bar">
|
|
<view class="action-item" @click="handleAction('whatsapp')">
|
|
<view class="action-icon-box">
|
|
<text class="action-icon-text">WA</text>
|
|
</view>
|
|
<text class="action-item-label">{{ textOf('portal.whatsapp') }}</text>
|
|
</view>
|
|
|
|
<view class="action-item" @click="handleAction('linkedin')">
|
|
<view class="action-icon-box">
|
|
<text class="action-icon-text">IN</text>
|
|
</view>
|
|
<text class="action-item-label">{{ textOf('portal.linkedin') }}</text>
|
|
</view>
|
|
|
|
<view class="action-item" @click="handleAction('email')">
|
|
<view class="action-icon-box">
|
|
<text class="action-icon-text">@</text>
|
|
</view>
|
|
<text class="action-item-label">{{ textOf('portal.email') }}</text>
|
|
</view>
|
|
|
|
<view class="action-item" @click="handleAction('phone')">
|
|
<view class="action-icon-box">
|
|
<text class="action-icon-text">TEL</text>
|
|
</view>
|
|
<text class="action-item-label">{{ textOf('portal.phone') }}</text>
|
|
</view>
|
|
|
|
<view class="action-item" @click="handleAction('telegram')">
|
|
<view class="action-icon-box">
|
|
<text class="action-icon-text">TG</text>
|
|
</view>
|
|
<text class="action-item-label">{{ textOf('portal.telegram') }}</text>
|
|
</view>
|
|
|
|
<view class="action-item" @click="handleAction('twitter')">
|
|
<view class="action-icon-box">
|
|
<text class="action-icon-text">X</text>
|
|
</view>
|
|
<text class="action-item-label">{{ textOf('portal.twitter') }}</text>
|
|
</view>
|
|
|
|
<view class="action-item" @click="handleAction('wechat')">
|
|
<view class="action-icon-box">
|
|
<text class="action-icon-text">WX</text>
|
|
</view>
|
|
<text class="action-item-label">{{ textOf('portal.wechat') }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 3. Bento Grid 网格展厅 -->
|
|
<view class="bento-grid">
|
|
<view
|
|
v-for="block in visibleBlocks"
|
|
:key="block.id"
|
|
class="bento-card"
|
|
:class="getColClass(block.colSpan)"
|
|
@click="handleBlockClick(block)"
|
|
>
|
|
<!-- 卡片类型标签 -->
|
|
<view class="bento-tag">
|
|
<text class="bento-tag-text">{{ getBlockTypeLabel(block.type) }}</text>
|
|
</view>
|
|
|
|
<!-- 卡片多媒体封面 (若有) -->
|
|
<image
|
|
v-if="block.mediaUrl"
|
|
class="bento-cover"
|
|
:src="block.mediaUrl"
|
|
mode="aspectFill"
|
|
/>
|
|
|
|
<!-- 卡片标题与内容 -->
|
|
<text v-if="block.title" class="bento-title">{{ block.title }}</text>
|
|
<text v-if="block.content" class="bento-content">{{ block.content }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 页面底部安全垫高,避免被常驻主 CTA 遮挡 -->
|
|
<view class="portal-bottom-spacer"></view>
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<text
|
|
v-if="state.card != null"
|
|
class="ai-entry"
|
|
@click="openAiChat"
|
|
>{{ textOf('card.ai_chat') }}</text>
|
|
|
|
<!-- 4. 单屏唯一定位主 CTA (保存到通讯录) -->
|
|
<view v-if="state.card != null" class="portal-cta-bar">
|
|
<button class="primary-cta-btn" @click="handleSaveContact">
|
|
<text class="primary-cta-btn-text">{{ textOf('card.save_contact') }}</text>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="uts">
|
|
import {
|
|
BentoBlockPublic,
|
|
} from '../../common/types.uts';
|
|
import {
|
|
createPortalState,
|
|
loadCardPortal,
|
|
reportPortalStay,
|
|
toggleAudioPronunciation,
|
|
triggerQuickAction,
|
|
getBentoColSpanClass,
|
|
filterVisibleBentoBlocks,
|
|
persistPortalLocale,
|
|
readStoredPortalLocale,
|
|
readCardSuggestedLocale,
|
|
resolvePortalLocale,
|
|
PortalQuickAction,
|
|
} from './usePortal.uts';
|
|
import { saveToContacts } from '../../common/vcard-export.uts';
|
|
import {
|
|
OmniLocale,
|
|
OmniMessageKey,
|
|
detectLocale,
|
|
getOmniText,
|
|
isRtl,
|
|
setCurrentLocale,
|
|
} from '../../common/locale.uts';
|
|
import I18nSelector from '../../components/i18n-selector/i18n-selector.uvue';
|
|
import AudioPronounce from '../../components/audio-pronounce/audio-pronounce.uvue';
|
|
import { releaseAudioPlayer } from './useAudioPlayer.uts';
|
|
|
|
const defaultAvatar = 'https://cdn.omnicard.ai/assets/default-avatar.png';
|
|
|
|
// 页面响应式状态
|
|
const state = reactive(createPortalState());
|
|
const currentCardId = ref('');
|
|
const currentLang = ref<OmniLocale>(detectLocale());
|
|
|
|
const cardAudioUrl = computed((): string => {
|
|
if (state.card == null) {
|
|
return '';
|
|
}
|
|
const primaryUrl = state.card!.audioPronounceUrl;
|
|
if (primaryUrl != null && primaryUrl.trim() !== '') {
|
|
return primaryUrl.trim();
|
|
}
|
|
const fallbackUrl = state.card!.audioPronunciationUrl;
|
|
if (fallbackUrl != null && fallbackUrl.trim() !== '') {
|
|
return fallbackUrl.trim();
|
|
}
|
|
return '';
|
|
});
|
|
|
|
const pageRtlClass = computed((): string => {
|
|
return isRtl(currentLang.value) ? 'portal-rtl' : '';
|
|
});
|
|
|
|
function textOf(key: OmniMessageKey): string {
|
|
return getOmniText(currentLang.value, key);
|
|
}
|
|
|
|
function applyResolvedLocale(next: OmniLocale): void {
|
|
setCurrentLocale(next);
|
|
currentLang.value = next;
|
|
}
|
|
|
|
function switchLang(next: OmniLocale): void {
|
|
applyResolvedLocale(next);
|
|
persistPortalLocale(next);
|
|
}
|
|
// 7 大语种动态切换支持: switchLang('zh-CN'); switchLang('en-US'); switchLang('ar-SA'); switchLang('es-ES'); switchLang('de-DE'); switchLang('fr-FR'); switchLang('ja-JP');
|
|
|
|
// 计算过滤后的 Bento 块
|
|
const visibleBlocks = computed((): BentoBlockPublic[] => {
|
|
if (!state.card || !state.card!.bentoBlocks) {
|
|
return [];
|
|
}
|
|
return filterVisibleBentoBlocks(state.card!.bentoBlocks);
|
|
});
|
|
|
|
// 样式类映射
|
|
function getColClass(colSpan: number): string {
|
|
return getBentoColSpanClass(colSpan);
|
|
}
|
|
|
|
// 块类型显示标签
|
|
function getBlockTypeLabel(type: string): string {
|
|
if (type === 'case') return textOf('portal.case');
|
|
if (type === 'article') return textOf('portal.article');
|
|
if (type === 'social') return textOf('portal.social');
|
|
if (type === 'video') return textOf('portal.video');
|
|
return textOf('portal.block');
|
|
}
|
|
|
|
// 播放发音
|
|
function handleAudioToggle(): void {
|
|
if (state.card && state.card!.audioPronunciationUrl) {
|
|
toggleAudioPronunciation(state, state.card!.audioPronunciationUrl);
|
|
}
|
|
}
|
|
|
|
// 快捷动作
|
|
function handleAction(type: PortalQuickAction): void {
|
|
if (!state.card) return;
|
|
let targetVal = '';
|
|
if (type === 'whatsapp') targetVal = state.card!.whatsapp || '';
|
|
if (type === 'linkedin') targetVal = state.card!.linkedin || '';
|
|
if (type === 'email') targetVal = state.card!.email || '';
|
|
if (type === 'phone') targetVal = state.card!.phone || '';
|
|
if (type === 'telegram') targetVal = state.card!.telegram || '';
|
|
if (type === 'twitter') targetVal = state.card!.twitter || '';
|
|
if (type === 'wechat') targetVal = state.card!.wechat || '';
|
|
triggerQuickAction(type, targetVal);
|
|
}
|
|
|
|
// 公司反向链接点击
|
|
function handleCompanyClick(): void {
|
|
if (!state.card) return;
|
|
uni.showToast({
|
|
title: state.card!.company,
|
|
icon: 'none',
|
|
});
|
|
}
|
|
|
|
function openExchange(): void {
|
|
if (!state.card) return;
|
|
const cardId = state.card!.id != null ? String(state.card!.id) : currentCardId.value;
|
|
const slug = state.card!.slug != null ? String(state.card!.slug) : '';
|
|
uni.navigateTo({
|
|
url: `/pages/portal/exchange?cardId=${cardId}&slug=${slug}`,
|
|
});
|
|
}
|
|
|
|
function openAiChat(): void {
|
|
if (!state.card) return;
|
|
const cardId = state.card!.id != null ? String(state.card!.id) : currentCardId.value;
|
|
uni.navigateTo({
|
|
url: `/pages/portal/ai-chat?cardId=${cardId}&lang=${currentLang.value}`,
|
|
});
|
|
}
|
|
|
|
// Bento 卡片点击
|
|
function handleBlockClick(block: BentoBlockPublic): void {
|
|
const blockId = block.id != null ? String(block.id) : '';
|
|
const cardQuery = currentCardId.value;
|
|
if (block.type === 'case') {
|
|
uni.navigateTo({
|
|
url: `/pages/portal/case-detail?cardId=${cardQuery}&blockId=${blockId}`,
|
|
});
|
|
return;
|
|
}
|
|
if (block.type === 'article') {
|
|
uni.navigateTo({
|
|
url: `/pages/portal/article-detail?cardId=${cardQuery}&blockId=${blockId}`,
|
|
});
|
|
return;
|
|
}
|
|
if (block.linkUrl && block.linkUrl!.trim() !== '') {
|
|
uni.setClipboardData({
|
|
data: block.linkUrl!,
|
|
success: () => {
|
|
uni.showToast({
|
|
title: textOf('portal.copied_link'),
|
|
icon: 'none',
|
|
});
|
|
},
|
|
});
|
|
} else if (block.title) {
|
|
uni.showToast({
|
|
title: block.title!,
|
|
icon: 'none',
|
|
});
|
|
}
|
|
}
|
|
|
|
// 保存至通讯录
|
|
function handleSaveContact(): void {
|
|
if (!state.card) return;
|
|
saveToContacts(state.card as any);
|
|
}
|
|
|
|
// 重新加载
|
|
function reloadPortal(): void {
|
|
if (currentCardId.value) {
|
|
loadCardPortal(state, currentCardId.value, state.touchType);
|
|
}
|
|
}
|
|
|
|
onLoad((options: any) => {
|
|
const queryId = (options && (options.id || options.slug)) ? String(options.id || options.slug) : 'default';
|
|
const querySource = (options && options.source) ? String(options.source) : 'link';
|
|
const queryLang = (options && options.lang) ? String(options.lang) : '';
|
|
currentCardId.value = queryId;
|
|
const stored = readStoredPortalLocale();
|
|
applyResolvedLocale(resolvePortalLocale(queryLang, stored, '', detectLocale()));
|
|
loadCardPortal(state, queryId, querySource).then((cardData) => {
|
|
const suggested = readCardSuggestedLocale(cardData);
|
|
applyResolvedLocale(resolvePortalLocale(queryLang, stored, suggested, detectLocale()));
|
|
});
|
|
});
|
|
|
|
onUnload(() => {
|
|
reportPortalStay(state, 100);
|
|
releaseAudioPlayer();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.portal-page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
background-color: #F7F8FA;
|
|
}
|
|
.portal-rtl {
|
|
direction: rtl;
|
|
text-align: right;
|
|
}
|
|
.locale-bar {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 8px 16px 0 16px;
|
|
}
|
|
.locale-selector-container {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
.locale-chip {
|
|
min-width: 44px;
|
|
min-height: 44px;
|
|
padding: 8px 12px;
|
|
font-size: 13px;
|
|
color: #0B5CFF;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.locale-chip-active {
|
|
color: #0B5CFF;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* RTL 镜像适配 */
|
|
.portal-rtl .hero-card {
|
|
text-align: right;
|
|
}
|
|
.portal-rtl .hero-name-row {
|
|
flex-direction: row;
|
|
justify-content: flex-start;
|
|
}
|
|
.portal-rtl .hero-name {
|
|
margin-right: 0;
|
|
margin-left: 12px;
|
|
}
|
|
.portal-rtl .hero-company-row {
|
|
justify-content: flex-start;
|
|
}
|
|
.portal-rtl .hero-company-name {
|
|
margin-right: 0;
|
|
margin-left: 4px;
|
|
}
|
|
.portal-rtl .hero-company-arrow {
|
|
display: inline-block;
|
|
transform: scaleX(-1);
|
|
}
|
|
.portal-rtl .pronounce-icon {
|
|
margin-right: 0;
|
|
margin-left: 4px;
|
|
}
|
|
.portal-rtl .ai-entry {
|
|
right: auto;
|
|
left: 16px;
|
|
}
|
|
|
|
.portal-scroll {
|
|
flex: 1;
|
|
}
|
|
|
|
.portal-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 16px;
|
|
}
|
|
|
|
/* 品牌栏 */
|
|
.brand-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 12px;
|
|
}
|
|
.brand-logo {
|
|
height: 28px;
|
|
}
|
|
|
|
/* 骨架屏 */
|
|
.portal-skeleton {
|
|
padding: 24px 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.skeleton-avatar {
|
|
width: 72px;
|
|
height: 72px;
|
|
border-radius: 36px;
|
|
background-color: #E3E7ED;
|
|
margin-bottom: 16px;
|
|
}
|
|
.skeleton-line {
|
|
height: 16px;
|
|
background-color: #E3E7ED;
|
|
border-radius: 8px;
|
|
margin-bottom: 12px;
|
|
}
|
|
.line-title {
|
|
width: 60%;
|
|
height: 24px;
|
|
}
|
|
.line-subtitle {
|
|
width: 40%;
|
|
}
|
|
.skeleton-card {
|
|
height: 180px;
|
|
background-color: #E3E7ED;
|
|
border-radius: 16px;
|
|
margin-top: 16px;
|
|
}
|
|
|
|
/* 异常提示 */
|
|
.portal-error-card {
|
|
margin: 40px 16px;
|
|
padding: 24px;
|
|
background-color: #FFFFFF;
|
|
border: 1px solid #E3E7ED;
|
|
border-radius: 16px;
|
|
align-items: center;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.error-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #101828;
|
|
margin-bottom: 8px;
|
|
}
|
|
.error-desc {
|
|
font-size: 14px;
|
|
color: #667085;
|
|
margin-bottom: 20px;
|
|
text-align: center;
|
|
}
|
|
.retry-btn {
|
|
height: 40px;
|
|
padding: 0 24px;
|
|
background-color: #0B5CFF;
|
|
border-radius: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
.retry-btn-text {
|
|
color: #FFFFFF;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* 1. Hero 身份卡片 */
|
|
.hero-card {
|
|
background-color: #FFFFFF;
|
|
border: 1px solid #E3E7ED;
|
|
border-radius: 16px;
|
|
padding: 24px 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
margin-bottom: 16px;
|
|
}
|
|
.hero-top-row {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 16px;
|
|
}
|
|
.hero-avatar {
|
|
width: 72px;
|
|
height: 72px;
|
|
border-radius: 36px;
|
|
border: 1px solid #E3E7ED;
|
|
background-color: #F7F8FA;
|
|
}
|
|
.hero-timezone-pill {
|
|
padding: 4px 10px;
|
|
background-color: #F7F8FA;
|
|
border: 1px solid #E3E7ED;
|
|
border-radius: 12px;
|
|
}
|
|
.hero-timezone-text {
|
|
font-size: 12px;
|
|
color: #667085;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.hero-name-row {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
margin-bottom: 6px;
|
|
}
|
|
.hero-name {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: #101828;
|
|
margin-right: 12px;
|
|
}
|
|
.pronounce-pill {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
padding: 4px 8px;
|
|
border-radius: 12px;
|
|
background-color: #EAF1FF;
|
|
}
|
|
.pronounce-pill-active {
|
|
background-color: #0B5CFF;
|
|
}
|
|
.pronounce-icon {
|
|
font-size: 10px;
|
|
color: #0B5CFF;
|
|
margin-right: 4px;
|
|
}
|
|
.pronounce-pill-active .pronounce-icon {
|
|
color: #FFFFFF;
|
|
}
|
|
.pronounce-label {
|
|
font-size: 12px;
|
|
color: #0B5CFF;
|
|
font-weight: 500;
|
|
}
|
|
.pronounce-pill-active .pronounce-label {
|
|
color: #FFFFFF;
|
|
}
|
|
|
|
.hero-title {
|
|
font-size: 15px;
|
|
color: #667085;
|
|
margin-bottom: 6px;
|
|
line-height: 1.4;
|
|
}
|
|
.hero-company-row {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
margin-bottom: 12px;
|
|
}
|
|
.hero-company-name {
|
|
font-size: 14px;
|
|
color: #101828;
|
|
font-weight: 600;
|
|
margin-right: 4px;
|
|
}
|
|
.hero-company-arrow {
|
|
font-size: 12px;
|
|
color: #0B5CFF;
|
|
}
|
|
.hero-slogan {
|
|
font-size: 13px;
|
|
color: #667085;
|
|
line-height: 1.5;
|
|
border-top: 1px solid #F7F8FA;
|
|
padding-top: 12px;
|
|
}
|
|
|
|
/* 2. 快捷动作条 */
|
|
.quick-action-bar {
|
|
display: flex;
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
justify-content: space-between;
|
|
margin-bottom: 16px;
|
|
}
|
|
.action-item {
|
|
flex: 1 1 20%;
|
|
min-width: 64px;
|
|
min-height: 52px;
|
|
background-color: #FFFFFF;
|
|
border: 1px solid #E3E7ED;
|
|
border-radius: 12px;
|
|
margin: 0 4px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 8px 4px;
|
|
}
|
|
.action-icon-box {
|
|
margin-bottom: 2px;
|
|
}
|
|
.action-icon-text {
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
color: #0B5CFF;
|
|
}
|
|
.action-item-label {
|
|
font-size: 11px;
|
|
color: #101828;
|
|
}
|
|
|
|
/* 3. Bento Grid 网格 */
|
|
.bento-grid {
|
|
display: flex;
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
justify-content: space-between;
|
|
}
|
|
.bento-card {
|
|
background-color: #FFFFFF;
|
|
border: 1px solid #E3E7ED;
|
|
border-radius: 16px;
|
|
padding: 16px;
|
|
margin-bottom: 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
box-sizing: border-box;
|
|
}
|
|
.bento-col-1 {
|
|
width: 48.5%;
|
|
}
|
|
.bento-col-2 {
|
|
width: 100%;
|
|
}
|
|
.bento-tag {
|
|
align-self: flex-start;
|
|
padding: 2px 8px;
|
|
background-color: #F7F8FA;
|
|
border-radius: 8px;
|
|
margin-bottom: 8px;
|
|
}
|
|
.bento-tag-text {
|
|
font-size: 11px;
|
|
color: #667085;
|
|
font-weight: 500;
|
|
}
|
|
.bento-cover {
|
|
width: 100%;
|
|
height: 120px;
|
|
border-radius: 8px;
|
|
margin-bottom: 10px;
|
|
background-color: #F7F8FA;
|
|
}
|
|
.bento-title {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: #101828;
|
|
margin-bottom: 6px;
|
|
line-height: 1.3;
|
|
}
|
|
.bento-content {
|
|
font-size: 13px;
|
|
color: #667085;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* 底部垫高与常驻主 CTA */
|
|
.portal-bottom-spacer {
|
|
height: 80px;
|
|
}
|
|
.ai-entry {
|
|
position: fixed;
|
|
right: 16px;
|
|
bottom: 96px;
|
|
min-height: 44px;
|
|
min-width: 44px;
|
|
padding: 8px 12px;
|
|
color: #7C5CFF;
|
|
font-size: 12px;
|
|
text-align: center;
|
|
}
|
|
.portal-cta-bar {
|
|
position: fixed;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
padding: 12px 16px 24px 16px;
|
|
background-color: #FFFFFF;
|
|
border-top: 1px solid #E3E7ED;
|
|
}
|
|
.primary-cta-btn {
|
|
height: 48px;
|
|
background-color: #0B5CFF;
|
|
border-radius: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
}
|
|
.primary-cta-btn-text {
|
|
color: #FFFFFF;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
}
|
|
</style>
|