文件
OmniCard/mpuniapp/pages/portal/exchange.uvue

398 行
9.9 KiB
Plaintext

<template>
<view class="exchange-page" :style="pageTheme">
<scroll-view class="exchange-scroll" :scroll-y="true">
<view class="exchange-content">
<view class="exchange-card">
<text class="exchange-title">互换名片</text>
<text class="exchange-desc">留下姓名与联系方式,方便继续沟通</text>
<view v-if="!state.canSubmit" class="exchange-warn">
<text class="exchange-warn-text">缺少名片标识,无法提交</text>
</view>
<!-- 纸质名片 OCR 拍照上传识别 -->
<view class="ocr-scan-banner">
<view class="ocr-scan-info">
<text class="ocr-scan-title">纸质名片 OCR 识别</text>
<text class="ocr-scan-subtitle">拍照或上传名片图片,快速自动填单</text>
</view>
<button
class="ocr-btn"
:class="{ 'ocr-btn-disabled': ocrState.scanning }"
:disabled="ocrState.scanning"
@click="handleOcrScan"
>
<text class="ocr-btn-text">{{ ocrState.scanning ? '识别中' : '拍摄/上传' }}</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">名片原图</text>
<text class="ocr-preview-hint">点击放大预览原图</text>
</view>
<view class="ocr-preview-delete" @click="handleRemoveOcrImage">
<text class="ocr-preview-delete-text">删除</text>
</view>
</view>
<view class="field">
<text class="field-label">姓名</text>
<input
class="field-input"
v-model="state.form.name"
placeholder="请输入姓名"
:disabled="!state.canSubmit || state.submitting"
/>
</view>
<view class="field">
<text class="field-label">公司</text>
<input
class="field-input"
v-model="state.form.company"
placeholder="选填"
:disabled="!state.canSubmit || state.submitting"
/>
</view>
<view class="field">
<text class="field-label">WhatsApp</text>
<input
class="field-input"
v-model="state.form.phone"
placeholder="WhatsApp 号码"
:disabled="!state.canSubmit || state.submitting"
/>
</view>
<view class="field">
<text class="field-label">邮箱</text>
<input
class="field-input"
v-model="state.form.email"
placeholder="工作邮箱"
:disabled="!state.canSubmit || state.submitting"
/>
</view>
<view class="field">
<text class="field-label">需求留言</text>
<textarea
class="field-textarea"
v-model="state.form.notes"
placeholder="选填,简要说明需求"
:disabled="!state.canSubmit || state.submitting"
/>
</view>
</view>
<view class="exchange-bottom-spacer"></view>
</view>
</scroll-view>
<view class="exchange-cta-bar">
<button
class="primary-cta-btn"
:class="{ 'primary-cta-btn-disabled': !state.canSubmit || state.submitting }"
:disabled="!state.canSubmit || state.submitting"
@click="handleSubmit"
>
<text class="primary-cta-btn-text">{{ state.submitting ? '提交中' : '提交留资' }}</text>
</button>
</view>
</view>
</template>
<script setup lang="uts">
import { utsLightColors, utsRadius, utsSpacing } from '../../common/tokens.uts';
import {
createExchangeState,
applyExchangeOptions,
submitExchange,
ExchangePageOptions,
} from './useExchange.uts';
import {
createOcrScanState,
handleCardOcrUpload,
previewOcrImage,
removeOcrImage,
} from './useOcrExchange.uts';
const pageTheme: Record<string, string> = {
'--oc-primary': utsLightColors.primary,
'--oc-primary-soft': utsLightColors.primarySoft,
'--oc-bg': utsLightColors.bg,
'--oc-surface': utsLightColors.surface,
'--oc-border': utsLightColors.border,
'--oc-text': utsLightColors.text,
'--oc-text-2': utsLightColors.text2,
'--oc-radius-btn': utsRadius.button,
'--oc-radius-card': utsRadius.card,
'--oc-space-8': utsSpacing.space8,
'--oc-space-16': utsSpacing.space16,
'--oc-space-24': utsSpacing.space24,
};
const state = reactive(createExchangeState());
const ocrState = reactive(createOcrScanState());
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> {
await submitExchange(state);
}
onLoad((options: ExchangePageOptions) => {
applyExchangeOptions(state, options);
});
</script>
<style scoped>
.ocr-preview-card {
display: flex;
flex-direction: row;
align-items: center;
padding: 8px 12px;
background-color: var(--oc-surface, #F7F8FA);
border: 1px solid var(--oc-border, #E3E7ED);
border-radius: var(--oc-radius-btn, 12px);
margin-bottom: var(--oc-space-16, 16px);
}
.ocr-preview-thumb-wrap {
width: 48px;
height: 48px;
border-radius: 8px;
overflow: hidden;
margin-right: 12px;
}
.ocr-preview-img {
width: 100%;
height: 100%;
}
.ocr-preview-info {
flex: 1;
display: flex;
flex-direction: column;
}
.ocr-preview-title {
font-size: 13px;
font-weight: 600;
color: var(--oc-text, #101828);
}
.ocr-preview-hint {
font-size: 11px;
color: var(--oc-primary, #0B5CFF);
margin-top: 2px;
}
.ocr-preview-delete {
padding: 4px 8px;
}
.ocr-preview-delete-text {
font-size: 12px;
color: var(--oc-text-2, #667085);
}
.exchange-page {
display: flex;
flex-direction: column;
height: 100%;
background-color: var(--oc-surface, #F7F8FA);
}
.exchange-scroll {
flex: 1;
}
.exchange-content {
display: flex;
flex-direction: column;
padding: var(--oc-space-16, 16px);
}
.exchange-card {
background-color: var(--oc-bg, #FFFFFF);
border: 1px solid var(--oc-border, #E3E7ED);
border-radius: var(--oc-radius-card, 16px);
padding: var(--oc-space-24, 24px) 20px;
display: flex;
flex-direction: column;
}
.exchange-title {
font-size: 24px;
font-weight: 700;
color: var(--oc-text, #101828);
margin-bottom: var(--oc-space-8, 8px);
line-height: 1.25;
}
.exchange-desc {
font-size: 14px;
color: var(--oc-text-2, #667085);
line-height: 1.6;
margin-bottom: var(--oc-space-24, 24px);
}
.exchange-warn {
padding: 12px var(--oc-space-16, 16px);
background-color: var(--oc-primary-soft, #EAF1FF);
border-radius: var(--oc-radius-btn, 12px);
margin-bottom: var(--oc-space-16, 16px);
}
.exchange-warn-text {
font-size: 13px;
color: var(--oc-primary, #0B5CFF);
line-height: 1.6;
}
.ocr-scan-banner {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 12px var(--oc-space-16, 16px);
background-color: var(--oc-surface, #F7F8FA);
border: 1px dashed var(--oc-border, #E3E7ED);
border-radius: var(--oc-radius-btn, 12px);
margin-bottom: var(--oc-space-16, 16px);
}
.ocr-scan-info {
display: flex;
flex-direction: column;
flex: 1;
margin-right: 12px;
}
.ocr-scan-title {
font-size: 14px;
font-weight: 600;
color: var(--oc-text, #101828);
margin-bottom: 2px;
}
.ocr-scan-subtitle {
font-size: 12px;
color: var(--oc-text-2, #667085);
line-height: 1.4;
}
.ocr-btn {
height: 36px;
padding: 0 14px;
background-color: var(--oc-primary-soft, #EAF1FF);
border: 1px solid var(--oc-primary, #0B5CFF);
border-radius: var(--oc-radius-btn, 12px);
display: flex;
align-items: center;
justify-content: center;
}
.ocr-btn-disabled {
opacity: 0.6;
}
.ocr-btn-text {
font-size: 13px;
font-weight: 600;
color: var(--oc-primary, #0B5CFF);
}
.field {
display: flex;
flex-direction: column;
margin-bottom: var(--oc-space-16, 16px);
}
.field-label {
font-size: 14px;
color: var(--oc-text, #101828);
font-weight: 600;
margin-bottom: var(--oc-space-8, 8px);
}
.field-input {
height: 44px;
padding: 0 12px;
background-color: var(--oc-surface, #F7F8FA);
border: 1px solid var(--oc-border, #E3E7ED);
border-radius: var(--oc-radius-btn, 12px);
font-size: 14px;
color: var(--oc-text, #101828);
}
.field-textarea {
min-height: 88px;
padding: 12px;
background-color: var(--oc-surface, #F7F8FA);
border: 1px solid var(--oc-border, #E3E7ED);
border-radius: var(--oc-radius-btn, 12px);
font-size: 14px;
color: var(--oc-text, #101828);
}
.exchange-bottom-spacer {
height: 80px;
}
.exchange-cta-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 12px var(--oc-space-16, 16px) var(--oc-space-24, 24px) var(--oc-space-16, 16px);
background-color: var(--oc-bg, #FFFFFF);
border-top: 1px solid var(--oc-border, #E3E7ED);
}
.primary-cta-btn {
height: 48px;
background-color: var(--oc-primary, #0B5CFF);
border-radius: var(--oc-radius-btn, 12px);
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
.primary-cta-btn-disabled {
background-color: var(--oc-border, #E3E7ED);
}
.primary-cta-btn-text {
color: var(--oc-bg, #FFFFFF);
font-size: 15px;
font-weight: 600;
}
.primary-cta-btn-disabled .primary-cta-btn-text {
color: var(--oc-text-2, #667085);
}
</style>