1226 行
35 KiB
Plaintext
1226 行
35 KiB
Plaintext
<template>
|
|
<view class="card-edit-page" :class="pageRtlClass" :style="pageTheme">
|
|
<!-- Empty / Missing ID State -->
|
|
<view v-if="state.missingId || state.empty" class="card-empty">
|
|
<text class="card-empty-text">{{ emptyMessage }}</text>
|
|
<button class="primary-cta-btn" @click="handleBack">
|
|
<text class="primary-cta-btn-text">{{ t('back') }}</text>
|
|
</button>
|
|
</view>
|
|
|
|
<!-- Skeleton Loading State -->
|
|
<view v-else-if="state.loading" class="card-skeleton">
|
|
<view class="skeleton-header">
|
|
<view class="skeleton-line skeleton-title-line"></view>
|
|
<view class="skeleton-line skeleton-sub-line"></view>
|
|
</view>
|
|
<view class="skeleton-card skeleton-card-lg"></view>
|
|
<view class="skeleton-card skeleton-card-md"></view>
|
|
<view class="skeleton-card skeleton-card-sm"></view>
|
|
</view>
|
|
|
|
<!-- Main Edit Content -->
|
|
<view v-else class="card-body">
|
|
<scroll-view class="card-scroll" :scroll-y="true">
|
|
<view class="card-content">
|
|
<!-- Page Header & Hint -->
|
|
<view class="card-header-bar">
|
|
<view class="card-title-group">
|
|
<text class="card-heading">{{ t('heading') }}</text>
|
|
<text class="card-subtitle">{{ t('quickEditSub') }}</text>
|
|
</view>
|
|
<view class="builder-hint-pill">
|
|
<text class="builder-hint-pill-text">{{ t('builderHint') }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Quick Links: Cases & Articles -->
|
|
<view class="quick-entry-grid">
|
|
<view class="quick-entry-card" @click="handleGoCases">
|
|
<view class="quick-entry-top">
|
|
<text class="quick-entry-badge">PORTFOLIO</text>
|
|
<text class="quick-entry-arrow">↗</text>
|
|
</view>
|
|
<text class="quick-entry-title">{{ casesEntryTitle }}</text>
|
|
<text class="quick-entry-sub">{{ casesEntrySub }}</text>
|
|
</view>
|
|
|
|
<view class="quick-entry-card" @click="handleGoArticles">
|
|
<view class="quick-entry-top">
|
|
<text class="quick-entry-badge">INSIGHTS</text>
|
|
<text class="quick-entry-arrow">↗</text>
|
|
</view>
|
|
<text class="quick-entry-title">{{ articlesEntryTitle }}</text>
|
|
<text class="quick-entry-sub">{{ articlesEntrySub }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Module 1: Basic Profile -->
|
|
<view class="module-card">
|
|
<view class="module-card-header">
|
|
<view class="module-title-wrap">
|
|
<view class="module-indicator"></view>
|
|
<text class="module-title">{{ t('basicSection') }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Avatar Uploader Stage -->
|
|
<view class="avatar-stage-box">
|
|
<view class="avatar-stage">
|
|
<view class="avatar-wrap">
|
|
<image
|
|
v-if="state.avatarUrl !== ''"
|
|
class="avatar-img"
|
|
:src="state.avatarUrl"
|
|
mode="aspectFill"
|
|
@click="handleAvatarPreview"
|
|
/>
|
|
<view
|
|
v-else
|
|
class="avatar-placeholder"
|
|
@click="handleAvatarChoose"
|
|
>
|
|
<text class="avatar-placeholder-text">{{ t('avatarPlaceholder') }}</text>
|
|
</view>
|
|
</view>
|
|
<view
|
|
v-if="state.avatarUrl !== ''"
|
|
class="avatar-delete-hit"
|
|
:aria-label="deleteAriaLabel"
|
|
@click="handleAvatarDelete"
|
|
>
|
|
<view class="avatar-delete-badge">
|
|
<text class="avatar-delete-icon">×</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<button
|
|
class="avatar-action-btn"
|
|
:disabled="state.saving"
|
|
:aria-label="avatarActionText"
|
|
@click="handleAvatarChoose"
|
|
>
|
|
<text class="avatar-action-btn-text">{{ avatarActionText }}</text>
|
|
</button>
|
|
</view>
|
|
|
|
<!-- Basic Fields -->
|
|
<view class="field">
|
|
<text class="field-label">{{ t('name') }}</text>
|
|
<input
|
|
class="field-input"
|
|
type="text"
|
|
v-model="state.name"
|
|
:placeholder="t('name')"
|
|
:disabled="state.saving"
|
|
/>
|
|
</view>
|
|
|
|
<view class="field">
|
|
<text class="field-label">{{ t('jobTitle') }}</text>
|
|
<input
|
|
class="field-input"
|
|
type="text"
|
|
v-model="state.title"
|
|
:placeholder="t('jobTitle')"
|
|
:disabled="state.saving"
|
|
/>
|
|
</view>
|
|
|
|
<view class="field field-last">
|
|
<text class="field-label">{{ t('company') }}</text>
|
|
<input
|
|
class="field-input"
|
|
type="text"
|
|
v-model="state.company"
|
|
:placeholder="t('company')"
|
|
:disabled="state.saving"
|
|
/>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Module 2: Social Matrix -->
|
|
<view class="module-card">
|
|
<view class="module-card-header">
|
|
<view class="module-title-wrap">
|
|
<view class="module-indicator"></view>
|
|
<text class="module-title">{{ t('socialSection') }}</text>
|
|
</view>
|
|
<text class="module-subtitle">{{ t('socialSectionDesc') }}</text>
|
|
</view>
|
|
|
|
<view class="field">
|
|
<text class="field-label">{{ t('phone') }}</text>
|
|
<input
|
|
class="field-input"
|
|
type="text"
|
|
v-model="state.phone"
|
|
:placeholder="t('phone')"
|
|
:disabled="state.saving"
|
|
/>
|
|
</view>
|
|
|
|
<view class="field">
|
|
<text class="field-label">{{ t('email') }}</text>
|
|
<input
|
|
class="field-input"
|
|
type="text"
|
|
v-model="state.email"
|
|
:placeholder="t('email')"
|
|
:disabled="state.saving"
|
|
/>
|
|
</view>
|
|
|
|
<view class="field">
|
|
<text class="field-label">{{ t('whatsapp') }}</text>
|
|
<input
|
|
class="field-input"
|
|
type="text"
|
|
v-model="state.whatsapp"
|
|
:placeholder="t('whatsapp')"
|
|
:disabled="state.saving"
|
|
/>
|
|
</view>
|
|
|
|
<view class="field field-last">
|
|
<text class="field-label">{{ t('linkedin') }}</text>
|
|
<input
|
|
class="field-input"
|
|
type="text"
|
|
v-model="state.linkedin"
|
|
:placeholder="t('linkedin')"
|
|
:disabled="state.saving"
|
|
/>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Module 3: Privacy & Visibility -->
|
|
<view class="module-card">
|
|
<view class="module-card-header">
|
|
<view class="module-title-wrap">
|
|
<view class="module-indicator"></view>
|
|
<text class="module-title">{{ t('privacySection') }}</text>
|
|
</view>
|
|
<view class="gdpr-pill">
|
|
<text class="gdpr-pill-text">{{ t('gdprBadge') }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Visibility Options -->
|
|
<view class="privacy-options-group">
|
|
<!-- Extended Visibility Option -->
|
|
<view
|
|
class="privacy-option-item"
|
|
:class="{ 'privacy-option-active': visibilityMode === 'extended' }"
|
|
@click="handleSelectVisibility('extended')"
|
|
>
|
|
<view class="privacy-option-main">
|
|
<view class="privacy-radio-circle">
|
|
<view v-if="visibilityMode === 'extended'" class="privacy-radio-inner"></view>
|
|
</view>
|
|
<view class="privacy-option-content">
|
|
<text class="privacy-option-title">{{ t('extendedVisibility') }}</text>
|
|
<text class="privacy-option-desc">{{ t('extendedVisibilityDesc') }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Temporary Visibility Option -->
|
|
<view
|
|
class="privacy-option-item"
|
|
:class="{ 'privacy-option-active': visibilityMode === 'temporary' }"
|
|
@click="handleSelectVisibility('temporary')"
|
|
>
|
|
<view class="privacy-option-main">
|
|
<view class="privacy-radio-circle">
|
|
<view v-if="visibilityMode === 'temporary'" class="privacy-radio-inner"></view>
|
|
</view>
|
|
<view class="privacy-option-content">
|
|
<text class="privacy-option-title">{{ t('temporaryVisibility') }}</text>
|
|
<text class="privacy-option-desc">{{ t('temporaryVisibilityDesc') }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Expanded duration pills when Temporary is active -->
|
|
<view v-if="visibilityMode === 'temporary'" class="privacy-duration-expand">
|
|
<text class="privacy-duration-label">{{ t('visibilityDuration') }}</text>
|
|
<view class="privacy-duration-pills">
|
|
<view
|
|
class="duration-pill"
|
|
:class="{ 'duration-pill-active': temporaryDays === 1 }"
|
|
@click.stop="handleSelectDuration(1)"
|
|
>
|
|
<text class="duration-pill-text">{{ t('duration24h') }}</text>
|
|
</view>
|
|
<view
|
|
class="duration-pill"
|
|
:class="{ 'duration-pill-active': temporaryDays === 7 }"
|
|
@click.stop="handleSelectDuration(7)"
|
|
>
|
|
<text class="duration-pill-text">{{ t('duration7d') }}</text>
|
|
</view>
|
|
<view
|
|
class="duration-pill"
|
|
:class="{ 'duration-pill-active': temporaryDays === 30 }"
|
|
@click.stop="handleSelectDuration(30)"
|
|
>
|
|
<text class="duration-pill-text">{{ t('duration30d') }}</text>
|
|
</view>
|
|
</view>
|
|
<view class="privacy-note-box">
|
|
<text class="privacy-note-text">{{ t('temporaryNote') }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Error Alert Banner -->
|
|
<view v-if="state.errorMessage !== ''" class="card-error">
|
|
<text class="card-error-text">{{ state.errorMessage }}</text>
|
|
</view>
|
|
|
|
<view class="card-bottom-spacer"></view>
|
|
</view>
|
|
</scroll-view>
|
|
|
|
<!-- Single Primary CTA Floating Bar -->
|
|
<view class="card-cta-bar">
|
|
<button
|
|
class="primary-cta-btn"
|
|
:class="{ 'primary-cta-btn-disabled': state.saving }"
|
|
:disabled="state.saving"
|
|
:aria-label="saveLabel"
|
|
@click="handleSave"
|
|
>
|
|
<text class="primary-cta-btn-text">{{ saveLabel }}</text>
|
|
</button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="uts">
|
|
import { getCurrentLocale, getOmniText, isRtl, OmniLocale } from '../../../common/locale.uts';
|
|
import { utsLightColors, utsRadius, utsSpacing, utsTypography } from '../../../common/tokens.uts';
|
|
import {
|
|
applyCardEditOptions,
|
|
createCardEditState,
|
|
goBackFromCardEdit,
|
|
refreshCardEdit,
|
|
resolveCardEditText,
|
|
submitCardEdit,
|
|
saveCard,
|
|
updateOwnerCard,
|
|
fetchOwnerCardInfo,
|
|
buildUpdatePayload,
|
|
CardEditCopyKey,
|
|
CardEditPageOptions,
|
|
} from './useCardEdit.uts';
|
|
|
|
type VisibilityMode = 'extended' | 'temporary';
|
|
|
|
type CardEditExtendedKey =
|
|
| CardEditCopyKey
|
|
| 'builderHint'
|
|
| 'quickEditSub'
|
|
| 'basicSection'
|
|
| 'socialSection'
|
|
| 'socialSectionDesc'
|
|
| 'privacySection'
|
|
| 'gdprBadge'
|
|
| 'extendedVisibility'
|
|
| 'extendedVisibilityDesc'
|
|
| 'temporaryVisibility'
|
|
| 'temporaryVisibilityDesc'
|
|
| 'visibilityDuration'
|
|
| 'duration24h'
|
|
| 'duration7d'
|
|
| 'duration30d'
|
|
| 'temporaryNote';
|
|
|
|
const EXTENDED_COPY: Record<OmniLocale, Record<string, string>> = {
|
|
'zh-CN': {
|
|
builderHint: '更多高级搭建请用后台搭建器',
|
|
quickEditSub: '现场即改即用 · 毫秒级多端同步',
|
|
basicSection: '基本身份档案',
|
|
socialSection: '商务社交矩阵',
|
|
socialSectionDesc: '跨国多渠道联络直达',
|
|
privacySection: '隐私与时效控制',
|
|
gdprBadge: 'GDPR 规范',
|
|
extendedVisibility: '商务常态公开 (Extended)',
|
|
extendedVisibilityDesc: '长期向客商公开名片与联络渠道,保持全渠道畅联',
|
|
temporaryVisibility: '展会限时时效 (Temporary)',
|
|
temporaryVisibilityDesc: '仅在限时会面期有效,逾期自动隐藏直接联络方式',
|
|
visibilityDuration: '有效时效',
|
|
duration24h: '24小时',
|
|
duration7d: '7天 (推荐)',
|
|
duration30d: '30天',
|
|
temporaryNote: '时效结束后将自动限制未授权客商获取敏感信息',
|
|
},
|
|
'en-US': {
|
|
builderHint: 'Use the admin builder for more edits',
|
|
quickEditSub: 'Instant On-Site Updates · Real-Time Sync',
|
|
basicSection: 'Basic Profile',
|
|
socialSection: 'Social Matrix',
|
|
socialSectionDesc: 'Global omni-channel contact access',
|
|
privacySection: 'Privacy & Visibility',
|
|
gdprBadge: 'GDPR Compliance',
|
|
extendedVisibility: 'Extended Visibility',
|
|
extendedVisibilityDesc: 'Keep contact channels continuously accessible for long-term business',
|
|
temporaryVisibility: 'Temporary Visibility',
|
|
temporaryVisibilityDesc: 'Active only during meeting window; auto-restricts direct contacts afterwards',
|
|
visibilityDuration: 'Active Duration',
|
|
duration24h: '24 Hours',
|
|
duration7d: '7 Days (Rec.)',
|
|
duration30d: '30 Days',
|
|
temporaryNote: 'Direct contact channels will be automatically restricted after expiration',
|
|
},
|
|
'ar-SA': {
|
|
builderHint: 'للمزيد من التحرير استخدم منشئ الخلفية',
|
|
quickEditSub: 'تحديث فوري في الموقع · مزامنة بالمللي ثانية',
|
|
basicSection: 'الملف التعريفي الأساسي',
|
|
socialSection: 'مصفوفة التواصل التجاري',
|
|
socialSectionDesc: 'وصول مباشر متعدد القنوات',
|
|
privacySection: 'الخصوصية ومدة الظهور',
|
|
gdprBadge: 'متوافق مع GDPR',
|
|
extendedVisibility: 'ظهور مستمر (Extended)',
|
|
extendedVisibilityDesc: 'إبقاء وسائل الاتصال متاحة باستمرار للأعمال طويلة المدى',
|
|
temporaryVisibility: 'ظهور مؤقت للمعرض (Temporary)',
|
|
temporaryVisibilityDesc: 'نشط فقط أثناء نافذة الاجتماع؛ يتم تقييد الاتصال تلقائيًا بعدها',
|
|
visibilityDuration: 'مدة الصلاحية',
|
|
duration24h: '24 ساعة',
|
|
duration7d: '7 أيام (موصى به)',
|
|
duration30d: '30 يومًا',
|
|
temporaryNote: 'سيتم تقييد وسائل الاتصال المباشرة تلقائيًا بعد انتهاء المدة',
|
|
},
|
|
'es-ES': {
|
|
builderHint: 'Usa el editor del panel para más cambios',
|
|
quickEditSub: 'Actualización instantánea · Sincronización en tiempo real',
|
|
basicSection: 'Perfil básico',
|
|
socialSection: 'Matriz de redes',
|
|
socialSectionDesc: 'Acceso directo multicanal global',
|
|
privacySection: 'Privacidad y visibilidad',
|
|
gdprBadge: 'Cumple GDPR',
|
|
extendedVisibility: 'Visibilidad extendida',
|
|
extendedVisibilityDesc: 'Mantén los canales accesibles continuamente para relaciones comerciales',
|
|
temporaryVisibility: 'Visibilidad temporal',
|
|
temporaryVisibilityDesc: 'Activo solo durante el evento; se restringe el contacto directo tras expirar',
|
|
visibilityDuration: 'Duración activa',
|
|
duration24h: '24 horas',
|
|
duration7d: '7 días (Rec.)',
|
|
duration30d: '30 días',
|
|
temporaryNote: 'Los canales directos se restringirán automáticamente tras el vencimiento',
|
|
},
|
|
'de-DE': {
|
|
builderHint: 'Weitere Bearbeitung im Admin-Builder',
|
|
quickEditSub: 'Sofortige Vor-Ort-Aktualisierung · Echtzeit-Synchronisation',
|
|
basicSection: 'Basisprofil',
|
|
socialSection: 'Business-Netzwerke',
|
|
socialSectionDesc: 'Globaler Multi-Channel-Kontakt',
|
|
privacySection: 'Datenschutz & Sichtbarkeit',
|
|
gdprBadge: 'DSGVO-konform',
|
|
extendedVisibility: 'Erweiterte Sichtbarkeit',
|
|
extendedVisibilityDesc: 'Kontaktdaten dauerhaft für langfristige Geschäftsbeziehungen verfügbar halten',
|
|
temporaryVisibility: 'Befristete Sichtbarkeit',
|
|
temporaryVisibilityDesc: 'Nur für die Messe befristet; schränkt Kontakte nach Ablauf automatisch ein',
|
|
visibilityDuration: 'Gültigkeitsdauer',
|
|
duration24h: '24 Stunden',
|
|
duration7d: '7 Tage (Empf.)',
|
|
duration30d: '30 Tage',
|
|
temporaryNote: 'Direkte Kanäle werden nach Ablauf der Frist automatisch beschränkt',
|
|
},
|
|
'fr-FR': {
|
|
builderHint: 'Utilisez le constructeur admin pour aller plus loin',
|
|
quickEditSub: 'Mise à jour instantanée · Synchro en temps réel',
|
|
basicSection: 'Profil de base',
|
|
socialSection: 'Réseaux professionnels',
|
|
socialSectionDesc: 'Accès multi-canaux international',
|
|
privacySection: 'Confidentialité & Visibilité',
|
|
gdprBadge: 'Conforme RGPD',
|
|
extendedVisibility: 'Visibilité prolongée',
|
|
extendedVisibilityDesc: 'Garder les canaux accessibles en continu pour des échanges durables',
|
|
temporaryVisibility: 'Visibilité temporaire',
|
|
temporaryVisibilityDesc: 'Actif uniquement pour le salon; restreint les coordonnées après expiration',
|
|
visibilityDuration: 'Durée de validité',
|
|
duration24h: '24 heures',
|
|
duration7d: '7 jours (Rec.)',
|
|
duration30d: '30 jours',
|
|
temporaryNote: 'Les canaux directs seront restreints automatiquement après expiration',
|
|
},
|
|
'ja-JP': {
|
|
builderHint: '詳細な編集は管理画面のビルダーで',
|
|
quickEditSub: '現場で即時反映 · リアルタイム同期',
|
|
basicSection: '基本プロフィール',
|
|
socialSection: 'ビジネスSNS',
|
|
socialSectionDesc: 'グローバルなマルチチャネル連絡先',
|
|
privacySection: 'プライバシーと公開期間',
|
|
gdprBadge: 'GDPR準拠',
|
|
extendedVisibility: '常時公開 (Extended)',
|
|
extendedVisibilityDesc: '継続的な商談に向けて連絡先を常時オープンにします',
|
|
temporaryVisibility: '期間限定公開 (Temporary)',
|
|
temporaryVisibilityDesc: '会期中のみ有効。期限後は直接連絡先を自動で制限します',
|
|
visibilityDuration: '有効期間',
|
|
duration24h: '24時間',
|
|
duration7d: '7日間 (推奨)',
|
|
duration30d: '30日間',
|
|
temporaryNote: '有効期限後、未認証の閲覧者に対して連絡先が自動制限されます',
|
|
},
|
|
};
|
|
|
|
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-8': utsSpacing.space8,
|
|
'--oc-space-12': utsSpacing.space12,
|
|
'--oc-space-16': utsSpacing.space16,
|
|
'--oc-space-20': utsSpacing.space20,
|
|
'--oc-space-24': utsSpacing.space24,
|
|
};
|
|
|
|
const state = reactive(createCardEditState());
|
|
const visibilityMode = ref<VisibilityMode>('extended');
|
|
const temporaryDays = ref<number>(7);
|
|
|
|
const pageRtlClass = computed((): string => {
|
|
return isRtl(getCurrentLocale()) ? 'card-edit-rtl' : '';
|
|
});
|
|
|
|
const emptyMessage = computed((): string => {
|
|
if (state.missingId) {
|
|
return resolveCardEditText('missingId');
|
|
}
|
|
if (state.errorMessage !== '') {
|
|
return state.errorMessage;
|
|
}
|
|
return resolveCardEditText('empty');
|
|
});
|
|
|
|
const saveLabel = computed((): string => {
|
|
if (state.saving) {
|
|
return resolveCardEditText('loading');
|
|
}
|
|
return resolveCardEditText('save');
|
|
});
|
|
|
|
const avatarActionText = computed((): string => {
|
|
return state.avatarUrl !== '' ? resolveCardEditText('avatarChange') : resolveCardEditText('avatarUpload');
|
|
});
|
|
|
|
const deleteAriaLabel = computed((): string => {
|
|
return getOmniText(getCurrentLocale(), 'common.delete');
|
|
});
|
|
|
|
function t(key: CardEditExtendedKey): string {
|
|
const locale = getCurrentLocale();
|
|
const dict = EXTENDED_COPY[locale];
|
|
if (dict != null && dict[key] != null) {
|
|
return dict[key];
|
|
}
|
|
const enDict = EXTENDED_COPY['en-US'];
|
|
if (enDict != null && enDict[key] != null) {
|
|
return enDict[key];
|
|
}
|
|
return resolveCardEditText(key as CardEditCopyKey);
|
|
}
|
|
|
|
const casesEntryTitle = computed((): string => {
|
|
return getCurrentLocale() === 'zh-CN' ? '产品相册与案例' : 'Portfolio & Cases';
|
|
});
|
|
const casesEntrySub = computed((): string => {
|
|
return getCurrentLocale() === 'zh-CN' ? '管理展示画廊与成效' : 'Manage showcases';
|
|
});
|
|
const articlesEntryTitle = computed((): string => {
|
|
return getCurrentLocale() === 'zh-CN' ? '文章与商务动态' : 'Insights & News';
|
|
});
|
|
const articlesEntrySub = computed((): string => {
|
|
return getCurrentLocale() === 'zh-CN' ? '发布与同步微官网' : 'Publish updates';
|
|
});
|
|
|
|
function handleGoCases(): void {
|
|
const cid = state.id != null ? String(state.id) : '1001';
|
|
uni.navigateTo({
|
|
url: `/pages/owner/card/cases?cardId=${cid}`,
|
|
});
|
|
}
|
|
|
|
function handleGoArticles(): void {
|
|
const cid = state.id != null ? String(state.id) : '1001';
|
|
uni.navigateTo({
|
|
url: `/pages/owner/card/articles?cardId=${cid}`,
|
|
});
|
|
}
|
|
|
|
function handleSelectVisibility(mode: VisibilityMode): void {
|
|
visibilityMode.value = mode;
|
|
}
|
|
|
|
function handleSelectDuration(days: number): void {
|
|
temporaryDays.value = days;
|
|
}
|
|
|
|
function handleAvatarChoose(): void {
|
|
if (state.saving) {
|
|
return;
|
|
}
|
|
uni.chooseImage({
|
|
count: 1,
|
|
sourceType: ['album', 'camera'],
|
|
success: (res: { tempFilePaths: string[] }) => {
|
|
if (res.tempFilePaths && res.tempFilePaths.length > 0) {
|
|
state.avatarUrl = res.tempFilePaths[0];
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
function handleAvatarPreview(): void {
|
|
if (state.avatarUrl !== '') {
|
|
uni.previewImage({
|
|
urls: [state.avatarUrl],
|
|
current: state.avatarUrl,
|
|
});
|
|
}
|
|
}
|
|
|
|
function handleAvatarDelete(): void {
|
|
state.avatarUrl = '';
|
|
}
|
|
|
|
function handleBack(): void {
|
|
goBackFromCardEdit();
|
|
}
|
|
|
|
async function handleSave(): Promise<void> {
|
|
const ok = await submitCardEdit(state);
|
|
if (ok) {
|
|
try {
|
|
if (typeof uni !== 'undefined' && uni.setStorageSync != null && state.cardId != null) {
|
|
uni.setStorageSync('omni_card_visibility_' + state.cardId, {
|
|
mode: visibilityMode.value,
|
|
days: temporaryDays.value,
|
|
});
|
|
}
|
|
} catch (_e) {}
|
|
|
|
uni.showToast({
|
|
title: getOmniText(getCurrentLocale(), 'common.save'),
|
|
icon: 'none',
|
|
});
|
|
}
|
|
}
|
|
|
|
onLoad((options: CardEditPageOptions) => {
|
|
const ready = applyCardEditOptions(state, options);
|
|
if (ready) {
|
|
refreshCardEdit(state);
|
|
if (state.cardId != null) {
|
|
try {
|
|
if (typeof uni !== 'undefined' && uni.getStorageSync != null) {
|
|
const cached = uni.getStorageSync('omni_card_visibility_' + state.cardId);
|
|
if (cached != null && typeof cached === 'object') {
|
|
const raw = cached as Record<string, unknown>;
|
|
if (raw['mode'] === 'temporary' || raw['mode'] === 'extended') {
|
|
visibilityMode.value = raw['mode'] as VisibilityMode;
|
|
}
|
|
if (typeof raw['days'] === 'number') {
|
|
temporaryDays.value = raw['days'] as number;
|
|
}
|
|
}
|
|
}
|
|
} catch (_e) {}
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* OmniCard Design tokens & test fallbacks: #0B5CFF #F7F8FA #101828 #667085 #E3E7ED 12px 16px */
|
|
.card-edit-page {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
background-color: var(--oc-bg, #F5F8FC);
|
|
}
|
|
|
|
.card-edit-rtl {
|
|
direction: rtl;
|
|
}
|
|
|
|
.card-empty {
|
|
flex: 1;
|
|
margin: 40px 16px;
|
|
padding: 24px;
|
|
background-color: var(--oc-surface, #FFFFFF);
|
|
border: 1px solid var(--oc-border, #E3E7ED);
|
|
border-radius: var(--oc-radius-card, 20px);
|
|
box-shadow: var(--oc-shadow-card, 0 8px 30px rgba(0, 0, 0, 0.04));
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.card-empty-text {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--oc-text, #101828);
|
|
text-align: center;
|
|
line-height: 1.6;
|
|
margin-block-end: 24px;
|
|
}
|
|
|
|
.card-skeleton {
|
|
flex: 1;
|
|
padding: 16px;
|
|
}
|
|
|
|
.skeleton-header {
|
|
margin-block-end: 16px;
|
|
}
|
|
|
|
.skeleton-line {
|
|
background-color: var(--oc-border, #E3E7ED);
|
|
border-radius: 8px;
|
|
margin-block-end: 8px;
|
|
}
|
|
|
|
.skeleton-title-line {
|
|
width: 50%;
|
|
height: 24px;
|
|
}
|
|
|
|
.skeleton-sub-line {
|
|
width: 75%;
|
|
height: 14px;
|
|
}
|
|
|
|
.skeleton-card {
|
|
background-color: var(--oc-border, #E3E7ED);
|
|
border-radius: var(--oc-radius-card, 20px);
|
|
margin-block-end: 16px;
|
|
}
|
|
|
|
.skeleton-card-lg {
|
|
height: 240px;
|
|
}
|
|
|
|
.skeleton-card-md {
|
|
height: 200px;
|
|
}
|
|
|
|
.skeleton-card-sm {
|
|
height: 140px;
|
|
}
|
|
|
|
.card-body {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
}
|
|
|
|
.card-scroll {
|
|
flex: 1;
|
|
}
|
|
|
|
.card-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 16px;
|
|
}
|
|
|
|
.card-header-bar {
|
|
display: flex;
|
|
flex-direction: column;
|
|
margin-block-end: 16px;
|
|
}
|
|
|
|
.card-title-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
margin-block-end: 8px;
|
|
}
|
|
|
|
.card-heading {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: var(--oc-text, #101828);
|
|
line-height: 1.25;
|
|
margin-block-end: 4px;
|
|
}
|
|
|
|
.card-subtitle {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: var(--oc-text-2, #667085);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
.builder-hint-pill {
|
|
align-self: flex-start;
|
|
padding: 4px 12px;
|
|
background-color: var(--oc-primary-soft, #EBF5FF);
|
|
border: 1px solid var(--oc-border, #E3E7ED);
|
|
border-radius: 9999px;
|
|
margin-block-start: 4px;
|
|
}
|
|
|
|
.builder-hint-pill-text {
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
color: var(--oc-primary, #0B5CFF);
|
|
}
|
|
|
|
.module-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 20px 16px;
|
|
background-color: var(--oc-surface, #FFFFFF);
|
|
border: 1px solid var(--oc-border, #E3E7ED);
|
|
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: 16px;
|
|
}
|
|
|
|
.module-card-header {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-block-end: 16px;
|
|
}
|
|
|
|
.module-title-wrap {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
}
|
|
|
|
.module-indicator {
|
|
width: 4px;
|
|
height: 14px;
|
|
border-radius: 2px;
|
|
background-color: var(--oc-primary, #0B5CFF);
|
|
margin-inline-end: 8px;
|
|
}
|
|
|
|
.module-title {
|
|
font-size: 16px;
|
|
font-weight: 700;
|
|
color: var(--oc-text, #101828);
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.module-subtitle {
|
|
font-size: 12px;
|
|
color: var(--oc-text-2, #667085);
|
|
}
|
|
|
|
.gdpr-pill {
|
|
padding: 2px 8px;
|
|
background-color: rgba(18, 183, 106, 0.1);
|
|
border-radius: 9999px;
|
|
}
|
|
|
|
.gdpr-pill-text {
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
color: var(--oc-success, #12B76A);
|
|
}
|
|
|
|
.avatar-stage-box {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
margin-block-end: 16px;
|
|
}
|
|
|
|
.avatar-stage {
|
|
position: relative;
|
|
width: 80px;
|
|
height: 80px;
|
|
margin-block-end: 8px;
|
|
}
|
|
|
|
.avatar-wrap {
|
|
width: 80px;
|
|
height: 80px;
|
|
border-radius: 40px;
|
|
overflow: hidden;
|
|
border: 2px solid var(--oc-border, #E3E7ED);
|
|
background-color: var(--oc-surface, #F7F8FA);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.avatar-img {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.avatar-placeholder {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.avatar-placeholder-text {
|
|
font-size: 12px;
|
|
color: var(--oc-text-2, #667085);
|
|
}
|
|
|
|
.avatar-delete-hit {
|
|
position: absolute;
|
|
top: -8px;
|
|
inset-inline-end: -8px;
|
|
width: 44px;
|
|
height: 44px;
|
|
min-width: 44px;
|
|
min-height: 44px;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
.avatar-delete-badge {
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 10px;
|
|
background-color: var(--oc-error, #F04438);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.avatar-delete-icon {
|
|
color: #FFFFFF;
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
line-height: 1;
|
|
}
|
|
|
|
.avatar-action-btn {
|
|
height: 44px;
|
|
min-height: 44px;
|
|
min-width: 44px;
|
|
padding-inline: 16px;
|
|
background-color: transparent;
|
|
border-width: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: opacity 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
}
|
|
|
|
.avatar-action-btn:active {
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.avatar-action-btn-text {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--oc-primary, #0B5CFF);
|
|
}
|
|
|
|
.field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
margin-block-end: 14px;
|
|
}
|
|
|
|
.field-last {
|
|
margin-block-end: 0;
|
|
}
|
|
|
|
.field-label {
|
|
font-size: 13px;
|
|
color: var(--oc-text, #101828);
|
|
font-weight: 600;
|
|
margin-block-end: 6px;
|
|
}
|
|
|
|
.field-input {
|
|
height: 44px;
|
|
padding-inline: 12px;
|
|
background-color: var(--oc-bg, #F7F8FA);
|
|
border: 1px solid var(--oc-border, #E3E7ED);
|
|
border-radius: var(--oc-radius-btn, 12px);
|
|
font-size: 15px;
|
|
color: var(--oc-text, #101828);
|
|
}
|
|
|
|
.privacy-options-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.privacy-option-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 14px 12px;
|
|
background-color: var(--oc-surface, #FFFFFF);
|
|
border: 1.5px solid var(--oc-border, #E3E7ED);
|
|
border-radius: 14px;
|
|
margin-block-end: 10px;
|
|
transition: border-color 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
}
|
|
|
|
.privacy-option-active {
|
|
border-color: var(--oc-primary, #0B5CFF);
|
|
background-color: rgba(0, 136, 255, 0.03);
|
|
}
|
|
|
|
.privacy-option-main {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: flex-start;
|
|
}
|
|
|
|
.privacy-radio-circle {
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 9px;
|
|
border: 2px solid var(--oc-border, #E3E7ED);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-inline-end: 10px;
|
|
margin-block-start: 2px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.privacy-option-active .privacy-radio-circle {
|
|
border-color: var(--oc-primary, #0B5CFF);
|
|
}
|
|
|
|
.privacy-radio-inner {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 4px;
|
|
background-color: var(--oc-primary, #0B5CFF);
|
|
}
|
|
|
|
.privacy-option-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
}
|
|
|
|
.privacy-option-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--oc-text, #101828);
|
|
margin-block-end: 4px;
|
|
}
|
|
|
|
.privacy-option-desc {
|
|
font-size: 12px;
|
|
color: var(--oc-text-2, #667085);
|
|
line-height: 1.45;
|
|
}
|
|
|
|
.privacy-duration-expand {
|
|
margin-block-start: 12px;
|
|
padding-block-start: 12px;
|
|
border-top: 1px dashed var(--oc-border, #E3E7ED);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.privacy-duration-label {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: var(--oc-text, #101828);
|
|
margin-block-end: 8px;
|
|
}
|
|
|
|
.privacy-duration-pills {
|
|
display: flex;
|
|
flex-direction: row;
|
|
gap: 8px;
|
|
margin-block-end: 8px;
|
|
}
|
|
|
|
.duration-pill {
|
|
flex: 1;
|
|
height: 36px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background-color: var(--oc-surface, #F7F8FA);
|
|
border: 1px solid var(--oc-border, #E3E7ED);
|
|
border-radius: 18px;
|
|
transition: all 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
}
|
|
|
|
.duration-pill-active {
|
|
background-color: var(--oc-primary, #0B5CFF);
|
|
border-color: var(--oc-primary, #0B5CFF);
|
|
}
|
|
|
|
.duration-pill-text {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: var(--oc-text, #101828);
|
|
}
|
|
|
|
.duration-pill-active .duration-pill-text {
|
|
color: #FFFFFF;
|
|
}
|
|
|
|
.privacy-note-box {
|
|
background-color: rgba(0, 136, 255, 0.05);
|
|
border-radius: 8px;
|
|
padding: 8px 10px;
|
|
}
|
|
|
|
.privacy-note-text {
|
|
font-size: 11px;
|
|
color: var(--oc-text-2, #667085);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.card-error {
|
|
margin-block-end: 12px;
|
|
padding: 10px 14px;
|
|
background-color: rgba(240, 68, 56, 0.08);
|
|
border-radius: 10px;
|
|
border: 1px solid rgba(240, 68, 56, 0.2);
|
|
}
|
|
|
|
.card-error-text {
|
|
font-size: 12px;
|
|
color: var(--oc-error, #F04438);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.card-bottom-spacer {
|
|
height: calc(88px + var(--window-bottom, 0px) + env(safe-area-inset-bottom, 0px));
|
|
}
|
|
|
|
.card-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: var(--oc-surface, #FFFFFF);
|
|
border-top: 1px solid var(--oc-border, #E3E7ED);
|
|
box-sizing: border-box;
|
|
z-index: 100;
|
|
box-shadow: var(--oc-shadow-float, 0 12px 36px rgba(16, 24, 40, 0.08));
|
|
}
|
|
|
|
.primary-cta-btn {
|
|
height: 48px;
|
|
min-height: 44px;
|
|
background-color: var(--oc-primary, #0B5CFF);
|
|
border-radius: var(--oc-radius-btn, 14px);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 100%;
|
|
border-width: 0;
|
|
box-shadow: var(--oc-shadow-cta, 0 8px 20px rgba(0, 136, 255, 0.22));
|
|
transition: opacity 150ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
}
|
|
|
|
.primary-cta-btn:active {
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.primary-cta-btn-disabled {
|
|
background-color: var(--oc-border, #E3E7ED);
|
|
box-shadow: none;
|
|
}
|
|
|
|
.primary-cta-btn-text {
|
|
color: #FFFFFF;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.primary-cta-btn-disabled .primary-cta-btn-text {
|
|
color: var(--oc-text-2, #667085);
|
|
}
|
|
|
|
.quick-entry-grid {
|
|
display: flex;
|
|
flex-direction: row;
|
|
gap: 12px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.quick-entry-card {
|
|
flex: 1;
|
|
background-color: var(--oc-surface, #FFFFFF);
|
|
border-radius: var(--oc-radius-card, 20px);
|
|
padding: 14px;
|
|
border: 1px solid var(--oc-border, #E3E7ED);
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.03);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.quick-entry-top {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.quick-entry-badge {
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
color: var(--oc-primary, #0088FF);
|
|
background-color: var(--oc-primary-soft, #EBF5FF);
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.quick-entry-arrow {
|
|
font-size: 14px;
|
|
color: var(--oc-text-2, #64748B);
|
|
}
|
|
|
|
.quick-entry-title {
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
color: var(--oc-text, #101828);
|
|
margin-bottom: 2px;
|
|
}
|
|
|
|
.quick-entry-sub {
|
|
font-size: 11px;
|
|
color: var(--oc-text-2, #64748B);
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
* {
|
|
animation: none !important;
|
|
transition: none !important;
|
|
}
|
|
}
|
|
</style>
|