493 行
12 KiB
Plaintext
493 行
12 KiB
Plaintext
<template>
|
||
<view class="card-edit-page" :style="pageTheme">
|
||
<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>
|
||
|
||
<view v-else-if="state.loading" class="card-skeleton">
|
||
<view class="skeleton-line skeleton-title"></view>
|
||
<view class="skeleton-card"></view>
|
||
<view class="skeleton-card"></view>
|
||
</view>
|
||
|
||
<view v-else class="card-body">
|
||
<scroll-view class="card-scroll" :scroll-y="true">
|
||
<view class="card-form">
|
||
<text class="card-heading">{{ t('heading') }}</text>
|
||
|
||
<!-- Card avatar upload and preview -->
|
||
<view class="avatar-field">
|
||
<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
|
||
v-if="state.avatarUrl !== ''"
|
||
class="avatar-delete-badge"
|
||
@click="handleAvatarDelete"
|
||
>
|
||
<text class="avatar-delete-icon">×</text>
|
||
</view>
|
||
</view>
|
||
<view class="avatar-actions">
|
||
<button
|
||
class="avatar-action-btn"
|
||
:disabled="state.saving"
|
||
@click="handleAvatarChoose"
|
||
>
|
||
<text class="avatar-action-btn-text">{{ avatarActionText }}</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
|
||
<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">
|
||
<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 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">
|
||
<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 v-if="state.errorMessage !== ''" class="card-error">
|
||
<text class="card-error-text">{{ state.errorMessage }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="card-bottom-spacer"></view>
|
||
</scroll-view>
|
||
|
||
<view class="card-cta-bar">
|
||
<button
|
||
class="primary-cta-btn"
|
||
:class="{ 'primary-cta-btn-disabled': state.saving }"
|
||
:disabled="state.saving"
|
||
@click="handleSave"
|
||
>
|
||
<text class="primary-cta-btn-text">{{ saveLabel }}</text>
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { getCurrentLocale, getOmniText } from '../../../common/locale.uts';
|
||
import { utsLightColors, utsRadius, utsSpacing } from '../../../common/tokens.uts';
|
||
import {
|
||
applyCardEditOptions,
|
||
createCardEditState,
|
||
goBackFromCardEdit,
|
||
refreshCardEdit,
|
||
resolveCardEditText,
|
||
submitCardEdit,
|
||
CardEditCopyKey,
|
||
CardEditPageOptions,
|
||
} from './useCardEdit.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-12': utsSpacing.space12,
|
||
'--oc-space-16': utsSpacing.space16,
|
||
'--oc-space-24': utsSpacing.space24,
|
||
};
|
||
|
||
const state = reactive(createCardEditState());
|
||
|
||
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');
|
||
});
|
||
|
||
function t(key: CardEditCopyKey): string {
|
||
return resolveCardEditText(key);
|
||
}
|
||
|
||
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) {
|
||
uni.showToast({
|
||
title: getOmniText(getCurrentLocale(), 'common.save'),
|
||
icon: 'none',
|
||
});
|
||
}
|
||
}
|
||
|
||
onLoad((options: CardEditPageOptions) => {
|
||
const ready = applyCardEditOptions(state, options);
|
||
if (ready) {
|
||
refreshCardEdit(state);
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<style scoped>
|
||
.avatar-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-bottom: var(--oc-space-16, 16px);
|
||
padding: var(--oc-space-12, 12px) 0;
|
||
}
|
||
|
||
.avatar-wrap {
|
||
position: relative;
|
||
width: 80px;
|
||
height: 80px;
|
||
border-radius: 40px;
|
||
overflow: hidden;
|
||
border: 2px solid var(--oc-primary, #0B5CFF);
|
||
background-color: var(--oc-surface, #F7F8FA);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.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-badge {
|
||
position: absolute;
|
||
top: 2px;
|
||
right: 2px;
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 9px;
|
||
background-color: rgba(240, 68, 56, 0.9);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.avatar-delete-icon {
|
||
color: #FFFFFF;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
}
|
||
|
||
.avatar-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
}
|
||
|
||
.avatar-action-btn {
|
||
padding: 4px 12px;
|
||
background-color: var(--oc-primary-soft, #EAF1FF);
|
||
border-radius: var(--oc-radius-btn, 12px);
|
||
border: none;
|
||
}
|
||
|
||
.avatar-action-btn-text {
|
||
font-size: 12px;
|
||
color: var(--oc-primary, #0B5CFF);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.card-edit-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
background-color: var(--oc-surface, #F7F8FA);
|
||
}
|
||
|
||
.card-empty {
|
||
flex: 1;
|
||
margin: 40px var(--oc-space-16, 16px);
|
||
padding: var(--oc-space-24, 24px);
|
||
background-color: var(--oc-bg, #FFFFFF);
|
||
border: 1px solid var(--oc-border, #E3E7ED);
|
||
border-radius: var(--oc-radius-card, 16px);
|
||
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-bottom: var(--oc-space-24, 24px);
|
||
}
|
||
|
||
.card-skeleton {
|
||
flex: 1;
|
||
padding-inline: var(--oc-space-16, 16px);
|
||
padding-block: var(--oc-space-16, 16px);
|
||
}
|
||
|
||
.skeleton-line {
|
||
height: 16px;
|
||
width: 40%;
|
||
background-color: var(--oc-border, #E3E7ED);
|
||
border-radius: 8px;
|
||
margin-bottom: var(--oc-space-12, 12px);
|
||
}
|
||
|
||
.skeleton-title {
|
||
width: 56%;
|
||
height: 24px;
|
||
}
|
||
|
||
.skeleton-card {
|
||
height: 72px;
|
||
background-color: var(--oc-border, #E3E7ED);
|
||
border-radius: var(--oc-radius-card, 16px);
|
||
margin-bottom: var(--oc-space-12, 12px);
|
||
}
|
||
|
||
.card-body {
|
||
display: flex;
|
||
flex-direction: column;
|
||
flex: 1;
|
||
}
|
||
|
||
.card-scroll {
|
||
flex: 1;
|
||
}
|
||
|
||
.card-form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
margin: var(--oc-space-16, 16px);
|
||
padding: var(--oc-space-24, 24px) 20px;
|
||
background-color: var(--oc-bg, #FFFFFF);
|
||
border: 1px solid var(--oc-border, #E3E7ED);
|
||
border-radius: var(--oc-radius-card, 16px);
|
||
}
|
||
|
||
.card-heading {
|
||
font-size: 24px;
|
||
font-weight: 700;
|
||
color: var(--oc-text, #101828);
|
||
line-height: 1.25;
|
||
margin-bottom: var(--oc-space-24, 24px);
|
||
}
|
||
|
||
.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-inline: 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);
|
||
}
|
||
|
||
.card-error {
|
||
margin-bottom: var(--oc-space-12, 12px);
|
||
}
|
||
|
||
.card-error-text {
|
||
font-size: 12px;
|
||
color: var(--oc-text-2, #667085);
|
||
}
|
||
|
||
.card-bottom-spacer {
|
||
height: 80px;
|
||
}
|
||
|
||
.card-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>
|