补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
1191 行
30 KiB
Plaintext
1191 行
30 KiB
Plaintext
<template>
|
||
<view class="exchange-page" :class="pageRtlClass" :style="pageTheme">
|
||
<!-- 顶部受控局部极光微光晕 (面积 < 8%) -->
|
||
<view class="ambient-aurora-glow"></view>
|
||
|
||
<!-- 顶部快捷返回栏 -->
|
||
<view class="exchange-top-bar">
|
||
<view class="top-back-btn" :aria-label="t('backPortal')" @click="handleBackPortal">
|
||
<text class="top-back-arrow">←</text>
|
||
<text class="top-back-text">{{ t('backPortal') }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<scroll-view class="exchange-scroll" :scroll-y="true">
|
||
<view class="exchange-content">
|
||
<!-- 1. Hero 身份与场景展台 -->
|
||
<view class="hero-stage">
|
||
<view class="hero-badge">
|
||
<view class="hero-badge-dot"></view>
|
||
<text class="hero-badge-text">{{ t('badge') }}</text>
|
||
</view>
|
||
<text class="hero-title">{{ t('title') }}</text>
|
||
<text class="hero-desc">{{ t('desc') }}</text>
|
||
</view>
|
||
|
||
<!-- 缺少名片标识阻断警告 -->
|
||
<view v-if="!state.canSubmit" class="exchange-warn">
|
||
<view class="warn-badge">
|
||
<text class="warn-badge-icon">!</text>
|
||
</view>
|
||
<text class="warn-text">{{ t('warnMissingId') }}</text>
|
||
</view>
|
||
|
||
<!-- 2. 留资成功反馈卡片 (Success State) -->
|
||
<view v-if="submitSuccess" class="success-card">
|
||
<view class="success-icon-wrap">
|
||
<view class="success-icon-ring"></view>
|
||
<view class="success-icon-tick"></view>
|
||
</view>
|
||
<text class="success-title">{{ t('successTitle') }}</text>
|
||
<text class="success-desc">{{ t('successDesc') }}</text>
|
||
|
||
<view class="success-summary">
|
||
<view class="summary-row">
|
||
<text class="summary-label">{{ t('nameLabel') }}:</text>
|
||
<text class="summary-val">{{ submittedName }}</text>
|
||
</view>
|
||
<view class="summary-row">
|
||
<text class="summary-label">{{ t('contactSectionTitle') }}:</text>
|
||
<text class="summary-val">{{ submittedContact }}</text>
|
||
</view>
|
||
<view v-if="submittedIntentLabel !== ''" class="summary-row">
|
||
<text class="summary-label">{{ t('intentTitle') }}:</text>
|
||
<text class="summary-val">{{ submittedIntentLabel }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<button class="primary-cta-btn" @click="handleBackPortal">
|
||
<text class="primary-cta-btn-text">{{ t('backPortal') }}</text>
|
||
</button>
|
||
<button class="secondary-action-btn" @click="handleResetAndAgain">
|
||
<text class="secondary-action-btn-text">{{ t('submitAgain') }}</text>
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 3. Bento Grid 表单容器区 (Active Form) -->
|
||
<view v-else class="bento-container">
|
||
<!-- Bento 模组 A: AI 纸质名片拍照/选图快速识别入库 -->
|
||
<view class="bento-card ocr-card">
|
||
<view class="ocr-top-row">
|
||
<view class="ocr-identity">
|
||
<view class="ocr-ai-icon-box">
|
||
<text class="ocr-ai-icon-text">SCAN</text>
|
||
</view>
|
||
<view class="ocr-titles">
|
||
<text class="ocr-title">{{ t('ocrTitle') }}</text>
|
||
<text class="ocr-subtitle">{{ t('ocrSubtitle') }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="ocr-ai-badge">
|
||
<text class="ocr-ai-badge-text">{{ t('badgeAi') }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="ocr-action-row">
|
||
<button
|
||
class="ocr-btn"
|
||
:class="{ 'ocr-btn-disabled': ocrState.scanning }"
|
||
:disabled="ocrState.scanning"
|
||
:aria-label="ocrCtaLabel"
|
||
@click="handleOcrScan"
|
||
>
|
||
<view class="ocr-camera-icon">
|
||
<view class="ocr-camera-lens"></view>
|
||
</view>
|
||
<text class="ocr-btn-text">{{ ocrCtaLabel }}</text>
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 已拍摄/上传名片缩略图预览与移除 -->
|
||
<view v-if="ocrState.imagePath !== ''" class="ocr-preview-card">
|
||
<view class="ocr-preview-thumb-wrap" @click="handlePreviewOcrImage">
|
||
<image class="ocr-preview-img" :src="ocrState.imagePath" mode="aspectFill" />
|
||
</view>
|
||
<view class="ocr-preview-info" @click="handlePreviewOcrImage">
|
||
<text class="ocr-preview-title">{{ t('ocrPreviewTitle') }}</text>
|
||
<text class="ocr-preview-hint">{{ t('ocrPreviewHint') }} ↗</text>
|
||
</view>
|
||
<view
|
||
class="ocr-preview-delete"
|
||
:aria-label="t('ocrDelete')"
|
||
@click="handleRemoveOcrImage"
|
||
>
|
||
<text class="ocr-preview-delete-text">{{ t('ocrDelete') }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Bento 模组 B: 核心商务联系信息 -->
|
||
<view class="bento-card contact-card">
|
||
<view class="card-section-head">
|
||
<text class="card-section-title">{{ t('contactSectionTitle') }}</text>
|
||
<text class="card-section-desc">{{ t('contactSectionDesc') }}</text>
|
||
</view>
|
||
|
||
<!-- 姓名 (必填) -->
|
||
<view class="field">
|
||
<view class="field-label-row">
|
||
<text class="field-label">{{ t('nameLabel') }}</text>
|
||
<text class="field-req-tag">{{ t('requiredMark') }}</text>
|
||
</view>
|
||
<input
|
||
class="field-input"
|
||
v-model="state.form.name"
|
||
:placeholder="t('namePlaceholder')"
|
||
:disabled="!state.canSubmit || state.submitting"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 所属公司 (选填) -->
|
||
<view class="field">
|
||
<view class="field-label-row">
|
||
<text class="field-label">{{ t('companyLabel') }}</text>
|
||
</view>
|
||
<input
|
||
class="field-input"
|
||
v-model="state.form.company"
|
||
:placeholder="t('companyPlaceholder')"
|
||
:disabled="!state.canSubmit || state.submitting"
|
||
/>
|
||
</view>
|
||
|
||
<!-- WhatsApp (全球常用) -->
|
||
<view class="field">
|
||
<view class="field-label-row">
|
||
<text class="field-label">{{ t('whatsappLabel') }}</text>
|
||
<view class="wa-pill">
|
||
<text class="wa-pill-text">{{ t('whatsappBadge') }}</text>
|
||
</view>
|
||
</view>
|
||
<input
|
||
class="field-input"
|
||
type="tel"
|
||
v-model="state.form.phone"
|
||
:placeholder="t('whatsappPlaceholder')"
|
||
:disabled="!state.canSubmit || state.submitting"
|
||
/>
|
||
</view>
|
||
|
||
<!-- 邮箱 -->
|
||
<view class="field">
|
||
<view class="field-label-row">
|
||
<text class="field-label">{{ t('emailLabel') }}</text>
|
||
</view>
|
||
<input
|
||
class="field-input"
|
||
type="email"
|
||
v-model="state.form.email"
|
||
:placeholder="t('emailPlaceholder')"
|
||
:disabled="!state.canSubmit || state.submitting"
|
||
/>
|
||
</view>
|
||
|
||
<view class="contact-rule-tip">
|
||
<text class="contact-rule-icon">ⓘ</text>
|
||
<text class="contact-rule-text">{{ t('whatsappOrEmailTip') }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Bento 模组 C: 商务意向快捷选择与需求留言 -->
|
||
<view class="bento-card intent-card">
|
||
<view class="card-section-head">
|
||
<text class="card-section-title">{{ t('intentTitle') }}</text>
|
||
</view>
|
||
|
||
<!-- 快捷意向胶囊标签 -->
|
||
<view class="intent-chips-wrap">
|
||
<view
|
||
v-for="item in intentList"
|
||
:key="item.key"
|
||
class="intent-chip"
|
||
:class="{ 'intent-chip-active': selectedIntentKey === item.key }"
|
||
@click="handleSelectIntent(item)"
|
||
>
|
||
<text
|
||
class="intent-chip-text"
|
||
:class="{ 'intent-chip-text-active': selectedIntentKey === item.key }"
|
||
>{{ t(item.labelKey) }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 备注文本域 -->
|
||
<view class="field field-notes">
|
||
<text class="field-label">{{ t('notesLabel') }}</text>
|
||
<textarea
|
||
class="field-textarea"
|
||
v-model="state.form.notes"
|
||
:placeholder="t('notesPlaceholder')"
|
||
:disabled="!state.canSubmit || state.submitting"
|
||
/>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- Bento 模组 D: 国际隐私合规与企业级安全背书 -->
|
||
<view class="bento-card privacy-card">
|
||
<view class="privacy-icon-box">
|
||
<view class="privacy-shield-badge">
|
||
<view class="shield-core-dot"></view>
|
||
</view>
|
||
</view>
|
||
<view class="privacy-text-wrap">
|
||
<text class="privacy-title">{{ t('privacyTitle') }}</text>
|
||
<text class="privacy-desc">{{ t('privacyDesc') }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部吸底主 CTA 留白防遮挡垫片 -->
|
||
<view v-if="!submitSuccess" class="exchange-bottom-spacer"></view>
|
||
</view>
|
||
</scroll-view>
|
||
|
||
<!-- 4. 单屏唯一主 CTA 悬浮底栏 -->
|
||
<view v-if="!submitSuccess" class="exchange-cta-bar">
|
||
<button
|
||
class="primary-cta-btn"
|
||
:class="{ 'primary-cta-btn-disabled': !state.canSubmit || state.submitting }"
|
||
:disabled="!state.canSubmit || state.submitting"
|
||
:aria-label="submitCtaLabel"
|
||
@click="handleSubmit"
|
||
>
|
||
<text class="primary-cta-btn-text">{{ submitCtaLabel }}</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 {
|
||
createExchangeState,
|
||
applyExchangeOptions,
|
||
submitExchange,
|
||
isExchangeSubmitSuccess,
|
||
resolveExchangeText,
|
||
ExchangePageOptions,
|
||
} from './useExchange.uts';
|
||
import {
|
||
createOcrScanState,
|
||
handleCardOcrUpload,
|
||
previewOcrImage,
|
||
removeOcrImage,
|
||
} from './useOcrExchange.uts';
|
||
|
||
type IntentItem = {
|
||
key: string;
|
||
labelKey: string;
|
||
};
|
||
|
||
const pageTheme: Record<string, string> = {
|
||
'--oc-primary': utsLightColors.primary,
|
||
'--oc-primary-gradient': utsLightColors.primaryGradient,
|
||
'--oc-primary-soft': utsLightColors.primarySoft,
|
||
'--oc-aurora': utsLightColors.aurora,
|
||
'--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(createExchangeState());
|
||
const ocrState = reactive(createOcrScanState());
|
||
|
||
const submitSuccess = ref<boolean>(false);
|
||
const submittedName = ref<string>('');
|
||
const submittedContact = ref<string>('');
|
||
const selectedIntentKey = ref<string>('');
|
||
|
||
const intentList: IntentItem[] = [
|
||
{ key: 'cooperate', labelKey: 'intentCooperate' },
|
||
{ key: 'purchase', labelKey: 'intentPurchase' },
|
||
{ key: 'inquiry', labelKey: 'intentInquiry' },
|
||
{ key: 'agent', labelKey: 'intentAgent' },
|
||
{ key: 'tech', labelKey: 'intentTech' },
|
||
];
|
||
|
||
const pageRtlClass = computed((): string => {
|
||
return isRtl(getCurrentLocale()) ? 'exchange-rtl' : '';
|
||
});
|
||
|
||
const ocrCtaLabel = computed((): string => {
|
||
return ocrState.scanning ? t('ocrScanning') : t('ocrScan');
|
||
});
|
||
|
||
const submitCtaLabel = computed((): string => {
|
||
return state.submitting ? t('submitting') : t('submit');
|
||
});
|
||
|
||
const submittedIntentLabel = computed((): string => {
|
||
if (selectedIntentKey.value === '') {
|
||
return '';
|
||
}
|
||
for (let i = 0; i < intentList.length; i++) {
|
||
if (intentList[i].key === selectedIntentKey.value) {
|
||
return t(intentList[i].labelKey);
|
||
}
|
||
}
|
||
return '';
|
||
});
|
||
|
||
function t(key: string): string {
|
||
return resolveExchangeText(key);
|
||
}
|
||
|
||
function handleSelectIntent(item: IntentItem): void {
|
||
const label = t(item.labelKey);
|
||
if (selectedIntentKey.value === item.key) {
|
||
selectedIntentKey.value = '';
|
||
return;
|
||
}
|
||
|
||
selectedIntentKey.value = item.key;
|
||
const currentNotes = state.form.notes.trim();
|
||
if (currentNotes === '') {
|
||
state.form.notes = `[${label}] `;
|
||
} else if (currentNotes.startsWith('[')) {
|
||
const endBracket = currentNotes.indexOf(']');
|
||
if (endBracket !== -1) {
|
||
state.form.notes = `[${label}]` + currentNotes.substring(endBracket + 1);
|
||
} else {
|
||
state.form.notes = `[${label}] ` + currentNotes;
|
||
}
|
||
} else {
|
||
state.form.notes = `[${label}] ` + currentNotes;
|
||
}
|
||
}
|
||
|
||
function handlePreviewOcrImage(): void {
|
||
previewOcrImage(ocrState);
|
||
}
|
||
|
||
function handleRemoveOcrImage(): void {
|
||
removeOcrImage(ocrState);
|
||
}
|
||
|
||
async function handleOcrScan(): Promise<void> {
|
||
if (ocrState.scanning) {
|
||
return;
|
||
}
|
||
await handleCardOcrUpload(state.form, ocrState);
|
||
}
|
||
|
||
async function handleSubmit(): Promise<void> {
|
||
if (!state.canSubmit || state.submitting) {
|
||
return;
|
||
}
|
||
const nameCache = state.form.name;
|
||
const contactCache = state.form.phone !== '' ? state.form.phone : state.form.email;
|
||
const lead = await submitExchange(state);
|
||
// 仅接口明确成功(含 persisted===true 或有效落库 id)才展示完成态
|
||
if (isExchangeSubmitSuccess(lead)) {
|
||
submittedName.value = nameCache;
|
||
submittedContact.value = contactCache;
|
||
submitSuccess.value = true;
|
||
}
|
||
}
|
||
|
||
function handleResetAndAgain(): void {
|
||
submitSuccess.value = false;
|
||
selectedIntentKey.value = '';
|
||
submittedName.value = '';
|
||
submittedContact.value = '';
|
||
}
|
||
|
||
function handleBackPortal(): void {
|
||
// #ifndef UNI-APP-X-TEST
|
||
try {
|
||
const pages = getCurrentPages();
|
||
if (pages.length > 1) {
|
||
uni.navigateBack();
|
||
return;
|
||
}
|
||
} catch (_e) {}
|
||
const targetId = state.cardId !== '' ? state.cardId : state.slug;
|
||
const url = targetId !== '' ? `/pages/portal/index?id=${targetId}` : '/pages/portal/index';
|
||
uni.reLaunch({ url });
|
||
// #endif
|
||
}
|
||
|
||
onLoad((options: ExchangePageOptions) => {
|
||
applyExchangeOptions(state, options);
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.exchange-page {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
background-color: var(--oc-bg, #F5F8FC);
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* 顶部受控局部极光弥散微光晕 (< 8%) */
|
||
.ambient-aurora-glow {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 180px;
|
||
background: radial-gradient(circle at 50% -20%, rgba(0, 210, 255, 0.12) 0%, rgba(0, 136, 255, 0.06) 45%, rgba(99, 102, 241, 0) 100%);
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
}
|
||
|
||
/* RTL 镜像适配 */
|
||
.exchange-rtl {
|
||
direction: rtl;
|
||
text-align: right;
|
||
}
|
||
.exchange-rtl .top-back-arrow {
|
||
transform: scaleX(-1);
|
||
}
|
||
.exchange-rtl .hero-stage,
|
||
.exchange-rtl .card-section-head,
|
||
.exchange-rtl .ocr-titles,
|
||
.exchange-rtl .privacy-text-wrap {
|
||
text-align: right;
|
||
}
|
||
.exchange-rtl .ocr-preview-hint {
|
||
transform: scaleX(-1);
|
||
display: inline-block;
|
||
}
|
||
|
||
/* 顶部返回导航栏 */
|
||
.exchange-top-bar {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
min-height: 44px;
|
||
padding-inline: 16px;
|
||
background-color: transparent;
|
||
z-index: 10;
|
||
}
|
||
|
||
.top-back-btn {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
min-height: 44px;
|
||
min-width: 44px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.top-back-arrow {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: var(--oc-primary, #0088FF);
|
||
margin-inline-end: 4px;
|
||
}
|
||
|
||
.top-back-text {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--oc-primary, #0088FF);
|
||
}
|
||
|
||
/* 滚动主视图 */
|
||
.exchange-scroll {
|
||
position: relative;
|
||
flex: 1;
|
||
z-index: 1;
|
||
}
|
||
|
||
.exchange-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-inline: 16px;
|
||
padding-block-start: 4px;
|
||
padding-block-end: 24px;
|
||
}
|
||
|
||
/* 1. Hero 展台 */
|
||
.hero-stage {
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin-block-end: 16px;
|
||
padding-inline: 4px;
|
||
}
|
||
|
||
.hero-badge {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
align-self: flex-start;
|
||
padding: 4px 12px;
|
||
background-color: var(--oc-primary-soft, #EBF5FF);
|
||
border: 1px solid rgba(0, 136, 255, 0.2);
|
||
border-radius: var(--oc-radius-pill, 9999px);
|
||
margin-block-end: 10px;
|
||
}
|
||
|
||
.hero-badge-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 3px;
|
||
background-color: var(--oc-primary, #0088FF);
|
||
margin-inline-end: 6px;
|
||
}
|
||
|
||
.hero-badge-text {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--oc-primary, #0088FF);
|
||
letter-spacing: 0.2px;
|
||
}
|
||
|
||
.hero-title {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: var(--oc-text, #101828);
|
||
line-height: 1.25;
|
||
margin-block-end: 6px;
|
||
}
|
||
|
||
.hero-desc {
|
||
font-size: 14px;
|
||
color: var(--oc-text-2, #64748B);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* 警示条 (缺少名片标识) */
|
||
.exchange-warn {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 12px 16px;
|
||
background-color: #FFF9F0;
|
||
border: 1px solid var(--oc-warning, #F79009);
|
||
border-radius: var(--oc-radius-btn, 14px);
|
||
margin-block-end: 16px;
|
||
}
|
||
|
||
.warn-badge {
|
||
width: 22px;
|
||
height: 22px;
|
||
border-radius: 11px;
|
||
background-color: var(--oc-warning, #F79009);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-inline-end: 10px;
|
||
}
|
||
|
||
.warn-badge-icon {
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
color: #FFFFFF;
|
||
}
|
||
|
||
.warn-text {
|
||
flex: 1;
|
||
font-size: 13px;
|
||
color: #B54708;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
/* 2. 留资成功反馈卡片 */
|
||
.success-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 32px 20px;
|
||
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));
|
||
margin-block-end: 24px;
|
||
text-align: center;
|
||
}
|
||
|
||
.success-icon-wrap {
|
||
position: relative;
|
||
width: 64px;
|
||
height: 64px;
|
||
border-radius: 32px;
|
||
background-color: rgba(18, 183, 106, 0.12);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-block-end: 16px;
|
||
}
|
||
|
||
.success-icon-ring {
|
||
position: absolute;
|
||
top: -4px;
|
||
left: -4px;
|
||
right: -4px;
|
||
bottom: -4px;
|
||
border: 2px dashed rgba(18, 183, 106, 0.35);
|
||
border-radius: 36px;
|
||
}
|
||
|
||
.success-icon-tick {
|
||
width: 14px;
|
||
height: 24px;
|
||
border-right: 3px solid var(--oc-success, #12B76A);
|
||
border-bottom: 3px solid var(--oc-success, #12B76A);
|
||
transform: rotate(45deg);
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.ocr-camera-icon {
|
||
width: 18px;
|
||
height: 14px;
|
||
border: 1.5px solid var(--oc-text, #101828);
|
||
border-radius: 3px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 6px;
|
||
}
|
||
|
||
.ocr-camera-lens {
|
||
width: 6px;
|
||
height: 6px;
|
||
border: 1.5px solid var(--oc-text, #101828);
|
||
border-radius: 9999px;
|
||
}
|
||
|
||
.success-title {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
color: var(--oc-text, #101828);
|
||
margin-block-end: 8px;
|
||
}
|
||
|
||
.success-desc {
|
||
font-size: 14px;
|
||
color: var(--oc-text-2, #64748B);
|
||
line-height: 1.6;
|
||
margin-block-end: 20px;
|
||
}
|
||
|
||
.success-summary {
|
||
width: 100%;
|
||
padding: 16px;
|
||
background-color: var(--oc-bg, #F5F8FC);
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
border-radius: 12px;
|
||
margin-block-end: 24px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.summary-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-block-end: 8px;
|
||
}
|
||
.summary-row:last-child {
|
||
margin-block-end: 0;
|
||
}
|
||
|
||
.summary-label {
|
||
font-size: 13px;
|
||
color: var(--oc-text-2, #64748B);
|
||
}
|
||
|
||
.summary-val {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--oc-text, #101828);
|
||
}
|
||
|
||
.secondary-action-btn {
|
||
margin-block-start: 12px;
|
||
height: 44px;
|
||
min-height: 44px;
|
||
background-color: transparent;
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
border-radius: var(--oc-radius-btn, 14px);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
cursor: pointer;
|
||
transition: opacity 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||
}
|
||
.secondary-action-btn:active {
|
||
opacity: 0.8;
|
||
}
|
||
.secondary-action-btn-text {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--oc-text-2, #64748B);
|
||
}
|
||
|
||
/* 3. Bento Grid 容器 */
|
||
.bento-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
/* Bento 卡片基础样式 */
|
||
.bento-card {
|
||
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: 20px 16px;
|
||
box-sizing: border-box;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* 模组 A: OCR 极速识别卡 */
|
||
.ocr-card {
|
||
background: linear-gradient(180deg, #FFFFFF 0%, #FAFCFF 100%);
|
||
border: 1px solid rgba(0, 136, 255, 0.15);
|
||
}
|
||
|
||
.ocr-top-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-block-end: 14px;
|
||
}
|
||
|
||
.ocr-identity {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex: 1;
|
||
}
|
||
|
||
.ocr-ai-icon-box {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 10px;
|
||
background: var(--oc-primary-gradient, linear-gradient(135deg, #00A3FF 0%, #0B63E5 100%));
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-inline-end: 10px;
|
||
}
|
||
|
||
.ocr-ai-icon-text {
|
||
font-size: 13px;
|
||
font-weight: 800;
|
||
color: #FFFFFF;
|
||
}
|
||
|
||
.ocr-titles {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.ocr-title {
|
||
font-size: 15px;
|
||
font-weight: 700;
|
||
color: var(--oc-text, #101828);
|
||
margin-block-end: 2px;
|
||
}
|
||
|
||
.ocr-subtitle {
|
||
font-size: 12px;
|
||
color: var(--oc-text-2, #64748B);
|
||
}
|
||
|
||
.ocr-ai-badge {
|
||
padding: 4px 8px;
|
||
background-color: var(--oc-primary-soft, #EBF5FF);
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.ocr-ai-badge-text {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
color: var(--oc-primary, #0088FF);
|
||
}
|
||
|
||
.ocr-action-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
}
|
||
|
||
.ocr-btn {
|
||
height: 44px;
|
||
min-height: 44px;
|
||
padding: 0 16px;
|
||
background-color: var(--oc-primary-soft, #EBF5FF);
|
||
border: 1px solid var(--oc-primary, #0088FF);
|
||
border-radius: var(--oc-radius-btn, 14px);
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
cursor: pointer;
|
||
transition: opacity 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||
}
|
||
.ocr-btn:active {
|
||
opacity: 0.85;
|
||
}
|
||
.ocr-btn-disabled {
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.ocr-btn-symbol {
|
||
font-size: 15px;
|
||
margin-inline-end: 6px;
|
||
}
|
||
|
||
.ocr-btn-text {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: var(--oc-primary, #0088FF);
|
||
}
|
||
|
||
/* 缩略图预览条 */
|
||
.ocr-preview-card {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 8px 12px;
|
||
background-color: #FFFFFF;
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
border-radius: 12px;
|
||
margin-block-start: 12px;
|
||
}
|
||
|
||
.ocr-preview-thumb-wrap {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
margin-inline-end: 12px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.ocr-preview-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.ocr-preview-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.ocr-preview-title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--oc-text, #101828);
|
||
}
|
||
|
||
.ocr-preview-hint {
|
||
font-size: 11px;
|
||
color: var(--oc-primary, #0088FF);
|
||
margin-block-start: 2px;
|
||
}
|
||
|
||
.ocr-preview-delete {
|
||
min-width: 44px;
|
||
min-height: 44px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding-inline: 8px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.ocr-preview-delete-text {
|
||
font-size: 12px;
|
||
color: var(--oc-error, #F04438);
|
||
}
|
||
|
||
/* 模组 B: 联系人表单卡 */
|
||
.card-section-head {
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin-block-end: 14px;
|
||
}
|
||
|
||
.card-section-title {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: var(--oc-text, #101828);
|
||
margin-block-end: 2px;
|
||
}
|
||
|
||
.card-section-desc {
|
||
font-size: 12px;
|
||
color: var(--oc-text-2, #64748B);
|
||
}
|
||
|
||
.field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin-block-end: 14px;
|
||
}
|
||
|
||
.field-label-row {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-block-end: 6px;
|
||
}
|
||
|
||
.field-label {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--oc-text, #101828);
|
||
}
|
||
|
||
.field-req-tag {
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
color: var(--oc-error, #F04438);
|
||
margin-inline-start: 4px;
|
||
}
|
||
|
||
.wa-pill {
|
||
padding: 2px 8px;
|
||
background-color: rgba(18, 183, 106, 0.1);
|
||
border-radius: var(--oc-radius-pill, 9999px);
|
||
margin-inline-start: 6px;
|
||
}
|
||
|
||
.wa-pill-text {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
color: var(--oc-success, #12B76A);
|
||
}
|
||
|
||
.field-input {
|
||
height: 44px;
|
||
min-height: 44px;
|
||
padding-inline: 14px;
|
||
background-color: var(--oc-bg, #F5F8FC);
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
border-radius: var(--oc-radius-btn, 14px);
|
||
font-size: 15px;
|
||
color: var(--oc-text, #101828);
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.contact-rule-tip {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 8px 12px;
|
||
background-color: var(--oc-primary-soft, #EBF5FF);
|
||
border-radius: 8px;
|
||
margin-block-start: 2px;
|
||
}
|
||
|
||
.contact-rule-icon {
|
||
font-size: 13px;
|
||
color: var(--oc-primary, #0088FF);
|
||
margin-inline-end: 6px;
|
||
}
|
||
|
||
.contact-rule-text {
|
||
font-size: 12px;
|
||
color: var(--oc-primary, #0088FF);
|
||
}
|
||
|
||
/* 模组 C: 意向快捷标签 */
|
||
.intent-chips-wrap {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-block-end: 14px;
|
||
}
|
||
|
||
.intent-chip {
|
||
padding: 8px 14px;
|
||
min-height: 36px;
|
||
background-color: var(--oc-bg, #F5F8FC);
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
border-radius: var(--oc-radius-pill, 9999px);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
transition: all 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||
}
|
||
.intent-chip:active {
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.intent-chip-active {
|
||
background-color: var(--oc-primary-soft, #EBF5FF);
|
||
border-color: var(--oc-primary, #0088FF);
|
||
}
|
||
|
||
.intent-chip-text {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
color: var(--oc-text-2, #64748B);
|
||
}
|
||
|
||
.intent-chip-text-active {
|
||
color: var(--oc-primary, #0088FF);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.field-notes {
|
||
margin-block-end: 0;
|
||
}
|
||
|
||
.field-textarea {
|
||
min-height: 84px;
|
||
padding: 12px 14px;
|
||
background-color: var(--oc-bg, #F5F8FC);
|
||
border: 1px solid var(--oc-border, #EBF1F7);
|
||
border-radius: var(--oc-radius-btn, 14px);
|
||
font-size: 14px;
|
||
color: var(--oc-text, #101828);
|
||
line-height: 1.5;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* 模组 D: 隐私承诺 */
|
||
.privacy-card {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: flex-start;
|
||
padding: 16px;
|
||
background-color: rgba(245, 248, 252, 0.7);
|
||
border: 1px dashed var(--oc-border, #EBF1F7);
|
||
box-shadow: none;
|
||
}
|
||
|
||
.privacy-icon-box {
|
||
margin-inline-end: 10px;
|
||
padding-block-start: 1px;
|
||
}
|
||
|
||
.privacy-shield-badge {
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 5px;
|
||
background-color: var(--oc-primary-soft, #EBF5FF);
|
||
border: 1px solid var(--oc-primary, #0088FF);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.shield-core-dot {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 9999px;
|
||
background-color: var(--oc-primary, #0088FF);
|
||
}
|
||
|
||
.privacy-text-wrap {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.privacy-title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--oc-text, #101828);
|
||
margin-block-end: 3px;
|
||
}
|
||
|
||
.privacy-desc {
|
||
font-size: 11px;
|
||
color: var(--oc-text-2, #64748B);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* 底部防遮挡垫片 */
|
||
.exchange-bottom-spacer {
|
||
height: calc(88px + var(--window-bottom, 0px) + env(safe-area-inset-bottom, 0px));
|
||
}
|
||
|
||
/* 4. 单屏唯一主 CTA 悬浮底栏 */
|
||
.exchange-cta-bar {
|
||
position: fixed;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: var(--window-bottom, 0px);
|
||
padding: 12px 16px calc(16px + env(safe-area-inset-bottom, 0px));
|
||
background-color: rgba(255, 255, 255, 0.96);
|
||
backdrop-filter: blur(12px);
|
||
border-top: 1px solid var(--oc-border, #EBF1F7);
|
||
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.03);
|
||
box-sizing: border-box;
|
||
z-index: 100;
|
||
}
|
||
|
||
.primary-cta-btn {
|
||
height: 48px;
|
||
min-height: 44px;
|
||
background: var(--oc-primary-gradient, linear-gradient(135deg, #00A3FF 0%, #0B63E5 100%));
|
||
border-radius: var(--oc-radius-btn, 14px);
|
||
box-shadow: var(--oc-shadow-cta, 0 8px 20px rgba(0, 136, 255, 0.22));
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
border-width: 0;
|
||
cursor: pointer;
|
||
transition: opacity 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||
}
|
||
.primary-cta-btn:active {
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.primary-cta-btn-disabled {
|
||
background: #E3E7ED !important;
|
||
box-shadow: none !important;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.primary-cta-btn-text {
|
||
color: #FFFFFF;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.3px;
|
||
}
|
||
|
||
.primary-cta-btn-disabled .primary-cta-btn-text {
|
||
color: var(--oc-text-2, #64748B);
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
* {
|
||
animation: none !important;
|
||
transition: none !important;
|
||
}
|
||
}
|
||
</style>
|