497 行
11 KiB
Plaintext
497 行
11 KiB
Plaintext
/**
|
|
* OmniCard AI (CRMmp) - 名片主极速编辑业务驱动层
|
|
* 拉名片详情,仅提交已填字段,不做 Bento 搭建
|
|
*/
|
|
|
|
import { CardProfile } from '../../../common/types.uts';
|
|
import { fetchOwnerCardInfo, updateOwnerCard } from '../../../common/api.uts';
|
|
import { getCurrentLocale, getOmniText } from '../../../common/locale.uts';
|
|
|
|
export type CardEditCopyKey =
|
|
| 'heading'
|
|
| 'name'
|
|
| 'jobTitle'
|
|
| 'company'
|
|
| 'phone'
|
|
| 'email'
|
|
| 'whatsapp'
|
|
| 'linkedin'
|
|
| 'save'
|
|
| 'loading'
|
|
| 'success'
|
|
| 'error'
|
|
| 'empty'
|
|
| 'missingId'
|
|
| 'back'
|
|
| 'avatarUpload'
|
|
| 'avatarChange'
|
|
| 'avatarPlaceholder';
|
|
|
|
export type CardEditForm = {
|
|
id?: string | number | null;
|
|
name?: string;
|
|
title?: string;
|
|
company?: string;
|
|
slogan?: string;
|
|
avatarUrl?: string;
|
|
phone?: string;
|
|
email?: string;
|
|
whatsapp?: string;
|
|
linkedin?: string;
|
|
telegram?: string;
|
|
twitter?: string;
|
|
wechat?: string;
|
|
};
|
|
|
|
export type CardUpdatePayload = {
|
|
id: string | number;
|
|
name?: string;
|
|
title?: string;
|
|
company?: string;
|
|
slogan?: string;
|
|
avatarUrl?: string;
|
|
phone?: string;
|
|
email?: string;
|
|
whatsapp?: string;
|
|
linkedin?: string;
|
|
telegram?: string;
|
|
twitter?: string;
|
|
wechat?: string;
|
|
};
|
|
|
|
export type LoadCardApi = {
|
|
fetchOwnerCardInfo?: (id: string | number) => Promise<CardProfile>;
|
|
};
|
|
|
|
export type LoadCardResult = {
|
|
card: CardProfile | null;
|
|
empty: boolean;
|
|
errorMessage: string;
|
|
};
|
|
|
|
export type SaveCardApi = {
|
|
updateOwnerCard?: (
|
|
payload: CardProfile & { id: string | number }
|
|
) => Promise<CardProfile>;
|
|
};
|
|
|
|
export type SaveCardResult = {
|
|
ok: boolean;
|
|
card: CardProfile | null;
|
|
errorMessage: string;
|
|
};
|
|
|
|
export type CardEditPageOptions = {
|
|
id?: string;
|
|
};
|
|
|
|
export type CardEditState = {
|
|
rawId: string;
|
|
cardId: number | null;
|
|
name: string;
|
|
title: string;
|
|
company: string;
|
|
avatarUrl: string;
|
|
phone: string;
|
|
email: string;
|
|
whatsapp: string;
|
|
linkedin: string;
|
|
loading: boolean;
|
|
saving: boolean;
|
|
empty: boolean;
|
|
missingId: boolean;
|
|
errorMessage: string;
|
|
};
|
|
|
|
function isZhLocale(): boolean {
|
|
return getCurrentLocale() === 'zh-CN';
|
|
}
|
|
|
|
/**
|
|
* 页面文案:保存 / 社交走词典,字段标签走本地词典
|
|
*/
|
|
export function resolveCardEditText(key: CardEditCopyKey): string {
|
|
const locale = getCurrentLocale();
|
|
if (key === 'save') {
|
|
return getOmniText(locale, 'common.save');
|
|
}
|
|
if (key === 'loading') {
|
|
return getOmniText(locale, 'common.loading');
|
|
}
|
|
if (key === 'success') {
|
|
return getOmniText(locale, 'common.success');
|
|
}
|
|
if (key === 'error') {
|
|
return getOmniText(locale, 'common.error');
|
|
}
|
|
if (key === 'empty') {
|
|
return getOmniText(locale, 'common.empty');
|
|
}
|
|
if (key === 'whatsapp') {
|
|
return getOmniText(locale, 'portal.whatsapp');
|
|
}
|
|
if (key === 'linkedin') {
|
|
return getOmniText(locale, 'portal.linkedin');
|
|
}
|
|
|
|
const zh = isZhLocale();
|
|
if (key === 'heading') {
|
|
return zh ? '极速编辑' : 'Quick edit';
|
|
}
|
|
if (key === 'name') {
|
|
return zh ? '姓名' : 'Name';
|
|
}
|
|
if (key === 'jobTitle') {
|
|
return zh ? '头衔' : 'Title';
|
|
}
|
|
if (key === 'company') {
|
|
return zh ? '公司' : 'Company';
|
|
}
|
|
if (key === 'phone') {
|
|
return zh ? '电话' : 'Phone';
|
|
}
|
|
if (key === 'email') {
|
|
return zh ? '邮箱' : 'Email';
|
|
}
|
|
if (key === 'missingId') {
|
|
return zh ? '缺少名片编号' : 'Card ID is missing';
|
|
}
|
|
if (key === 'back') {
|
|
return zh ? '返回名片' : 'Back to card';
|
|
}
|
|
if (key === 'avatarUpload') {
|
|
return zh ? '点击拍照/上传' : 'Upload photo';
|
|
}
|
|
if (key === 'avatarChange') {
|
|
return zh ? '更换头像' : 'Change avatar';
|
|
}
|
|
if (key === 'avatarPlaceholder') {
|
|
return zh ? '上传头像' : 'Upload avatar';
|
|
}
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* 解析为正整数。非正整数返回 null
|
|
*/
|
|
export function parseCardId(
|
|
raw: string | number | null | undefined
|
|
): number | null {
|
|
if (raw == null) {
|
|
return null;
|
|
}
|
|
if (typeof raw === 'number') {
|
|
if (raw <= 0) {
|
|
return null;
|
|
}
|
|
if (Math.floor(raw) !== raw) {
|
|
return null;
|
|
}
|
|
return raw;
|
|
}
|
|
const text = String(raw).trim();
|
|
if (text === '') {
|
|
return null;
|
|
}
|
|
if (!/^[1-9]\d*$/.test(text)) {
|
|
return null;
|
|
}
|
|
return Number(text);
|
|
}
|
|
|
|
function pickFilled(value: string | null | undefined): string | null {
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
const text = String(value).trim();
|
|
if (text === '') {
|
|
return null;
|
|
}
|
|
return text;
|
|
}
|
|
|
|
function assignFilled(
|
|
payload: CardUpdatePayload,
|
|
key:
|
|
| 'name'
|
|
| 'title'
|
|
| 'company'
|
|
| 'slogan'
|
|
| 'avatarUrl'
|
|
| 'phone'
|
|
| 'email'
|
|
| 'whatsapp'
|
|
| 'linkedin'
|
|
| 'telegram'
|
|
| 'twitter'
|
|
| 'wechat',
|
|
value: string | null | undefined
|
|
): void {
|
|
const filled = pickFilled(value);
|
|
if (filled != null) {
|
|
payload[key] = filled;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 只带已填字段与解析后的 id。缺 id 返回 null
|
|
*/
|
|
export function buildUpdatePayload(form: CardEditForm): CardUpdatePayload | null {
|
|
const id = parseCardId(form.id);
|
|
if (id == null) {
|
|
return null;
|
|
}
|
|
const payload: CardUpdatePayload = { id };
|
|
assignFilled(payload, 'name', form.name);
|
|
assignFilled(payload, 'title', form.title);
|
|
assignFilled(payload, 'company', form.company);
|
|
assignFilled(payload, 'slogan', form.slogan);
|
|
assignFilled(payload, 'avatarUrl', form.avatarUrl);
|
|
assignFilled(payload, 'phone', form.phone);
|
|
assignFilled(payload, 'email', form.email);
|
|
assignFilled(payload, 'whatsapp', form.whatsapp);
|
|
assignFilled(payload, 'linkedin', form.linkedin);
|
|
assignFilled(payload, 'telegram', form.telegram);
|
|
assignFilled(payload, 'twitter', form.twitter);
|
|
assignFilled(payload, 'wechat', form.wechat);
|
|
return payload;
|
|
}
|
|
|
|
function readErrorMessage(err: unknown): string {
|
|
if (err instanceof Error && err.message !== '') {
|
|
return err.message;
|
|
}
|
|
return resolveCardEditText('error');
|
|
}
|
|
|
|
function asUpdatePayload(
|
|
payload: CardUpdatePayload
|
|
): CardProfile & { id: string | number } {
|
|
return payload as CardProfile & { id: string | number };
|
|
}
|
|
|
|
/**
|
|
* 拉取名片详情。可注入 fetchOwnerCardInfo。缺 id 不请求
|
|
*/
|
|
export async function loadCard(
|
|
id: string | number | null | undefined,
|
|
api?: LoadCardApi
|
|
): Promise<LoadCardResult> {
|
|
const cardId = parseCardId(id);
|
|
if (cardId == null) {
|
|
return {
|
|
card: null,
|
|
empty: true,
|
|
errorMessage: '',
|
|
};
|
|
}
|
|
|
|
const fetchInfo =
|
|
api != null && api.fetchOwnerCardInfo != null
|
|
? api.fetchOwnerCardInfo
|
|
: fetchOwnerCardInfo;
|
|
|
|
try {
|
|
const card = await fetchInfo(cardId);
|
|
return {
|
|
card,
|
|
empty: false,
|
|
errorMessage: '',
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
card: null,
|
|
empty: true,
|
|
errorMessage: readErrorMessage(err),
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 保存名片。可注入 updateOwnerCard。缺 id 不请求
|
|
*/
|
|
export async function saveCard(
|
|
form: CardEditForm,
|
|
api?: SaveCardApi
|
|
): Promise<SaveCardResult> {
|
|
const payload = buildUpdatePayload(form);
|
|
if (payload == null) {
|
|
return {
|
|
ok: false,
|
|
card: null,
|
|
errorMessage: '',
|
|
};
|
|
}
|
|
|
|
const update =
|
|
api != null && api.updateOwnerCard != null
|
|
? api.updateOwnerCard
|
|
: updateOwnerCard;
|
|
|
|
try {
|
|
const card = await update(asUpdatePayload(payload));
|
|
return {
|
|
ok: true,
|
|
card,
|
|
errorMessage: '',
|
|
};
|
|
} catch (err) {
|
|
return {
|
|
ok: false,
|
|
card: null,
|
|
errorMessage: readErrorMessage(err),
|
|
};
|
|
}
|
|
}
|
|
|
|
export function createCardEditState(): CardEditState {
|
|
return {
|
|
rawId: '',
|
|
cardId: null,
|
|
name: '',
|
|
title: '',
|
|
company: '',
|
|
avatarUrl: '',
|
|
phone: '',
|
|
email: '',
|
|
whatsapp: '',
|
|
linkedin: '',
|
|
loading: false,
|
|
saving: false,
|
|
empty: false,
|
|
missingId: false,
|
|
errorMessage: '',
|
|
};
|
|
}
|
|
|
|
export function applyCardEditOptions(
|
|
state: CardEditState,
|
|
options: CardEditPageOptions | null
|
|
): boolean {
|
|
if (options == null || options.id == null) {
|
|
state.rawId = '';
|
|
state.cardId = null;
|
|
state.missingId = true;
|
|
state.empty = true;
|
|
return false;
|
|
}
|
|
const parsed = parseCardId(options.id);
|
|
state.rawId = String(options.id).trim();
|
|
state.cardId = parsed;
|
|
state.missingId = parsed == null;
|
|
state.empty = parsed == null;
|
|
return parsed != null;
|
|
}
|
|
|
|
function readOptionalText(value: string | null | undefined): string {
|
|
if (value == null) {
|
|
return '';
|
|
}
|
|
return String(value);
|
|
}
|
|
|
|
function applyCardToState(state: CardEditState, card: CardProfile): void {
|
|
if (card.id != null) {
|
|
const parsed = parseCardId(card.id);
|
|
if (parsed != null) {
|
|
state.cardId = parsed;
|
|
}
|
|
}
|
|
state.name = card.name != null ? String(card.name) : '';
|
|
state.title = readOptionalText(card.title);
|
|
state.company = readOptionalText(card.company);
|
|
state.avatarUrl = readOptionalText(card.avatarUrl);
|
|
state.phone = readOptionalText(card.phone);
|
|
state.email = readOptionalText(card.email);
|
|
state.whatsapp = readOptionalText(card.whatsapp);
|
|
state.linkedin = readOptionalText(card.linkedin);
|
|
}
|
|
|
|
export function applyLoadResult(
|
|
state: CardEditState,
|
|
result: LoadCardResult
|
|
): void {
|
|
if (result.card != null) {
|
|
applyCardToState(state, result.card);
|
|
state.empty = false;
|
|
state.missingId = false;
|
|
} else {
|
|
state.empty = true;
|
|
}
|
|
state.errorMessage = result.errorMessage;
|
|
}
|
|
|
|
export function formFromState(state: CardEditState): CardEditForm {
|
|
return {
|
|
id: state.cardId,
|
|
name: state.name,
|
|
title: state.title,
|
|
company: state.company,
|
|
avatarUrl: state.avatarUrl,
|
|
phone: state.phone,
|
|
email: state.email,
|
|
whatsapp: state.whatsapp,
|
|
linkedin: state.linkedin,
|
|
};
|
|
}
|
|
|
|
export async function refreshCardEdit(
|
|
state: CardEditState,
|
|
api?: LoadCardApi
|
|
): Promise<LoadCardResult> {
|
|
if (state.missingId || state.cardId == null) {
|
|
const skipped: LoadCardResult = {
|
|
card: null,
|
|
empty: true,
|
|
errorMessage: '',
|
|
};
|
|
applyLoadResult(state, skipped);
|
|
state.missingId = true;
|
|
return skipped;
|
|
}
|
|
if (state.loading) {
|
|
return {
|
|
card: null,
|
|
empty: state.empty,
|
|
errorMessage: state.errorMessage,
|
|
};
|
|
}
|
|
state.loading = true;
|
|
state.errorMessage = '';
|
|
const result = await loadCard(state.cardId, api);
|
|
applyLoadResult(state, result);
|
|
state.loading = false;
|
|
return result;
|
|
}
|
|
|
|
export async function submitCardEdit(
|
|
state: CardEditState,
|
|
api?: SaveCardApi
|
|
): Promise<boolean> {
|
|
if (state.cardId == null || state.saving) {
|
|
return false;
|
|
}
|
|
state.saving = true;
|
|
state.errorMessage = '';
|
|
const result = await saveCard(formFromState(state), api);
|
|
if (result.ok && result.card != null) {
|
|
applyCardToState(state, result.card);
|
|
state.saving = false;
|
|
return true;
|
|
}
|
|
state.saving = false;
|
|
state.errorMessage = result.errorMessage;
|
|
return false;
|
|
}
|
|
|
|
export function goBackFromCardEdit(): void {
|
|
uni.navigateBack({
|
|
delta: 1,
|
|
fail: () => {
|
|
uni.reLaunch({
|
|
url: '/pages/owner/card/index',
|
|
});
|
|
},
|
|
});
|
|
}
|