补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。 Co-authored-by: Cursor <cursoragent@cursor.com>
864 行
20 KiB
Plaintext
864 行
20 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 { fetchOwnerCardInfo, updateOwnerCard };
|
||
|
||
export type CardEditCopyKey =
|
||
| 'heading'
|
||
| 'name'
|
||
| 'jobTitle'
|
||
| 'company'
|
||
| 'slogan'
|
||
| 'phone'
|
||
| 'email'
|
||
| 'whatsapp'
|
||
| 'linkedin'
|
||
| 'timezone'
|
||
| 'save'
|
||
| 'loading'
|
||
| 'success'
|
||
| 'error'
|
||
| 'empty'
|
||
| 'missingId'
|
||
| 'back'
|
||
| 'avatarUpload'
|
||
| 'avatarChange'
|
||
| 'avatarPlaceholder'
|
||
| 'lockedHint'
|
||
| 'moduleOrderSection'
|
||
| 'moveUp'
|
||
| 'moveDown';
|
||
|
||
/** 极速编辑页可重排模块(无服务端排序字段,仅客户端顺序) */
|
||
export type CardEditModuleKey = 'basic' | 'social' | 'privacy';
|
||
|
||
export const DEFAULT_MODULE_ORDER: CardEditModuleKey[] = [
|
||
'basic',
|
||
'social',
|
||
'privacy',
|
||
];
|
||
|
||
const MODULE_ORDER_STORAGE_PREFIX = 'omni_card_module_order_';
|
||
|
||
/** 品牌套件可锁定字段(接口 lockedFields / whitelist 原样或别名) */
|
||
export type BrandLockFieldKey =
|
||
| 'company'
|
||
| 'companyName'
|
||
| 'logo'
|
||
| 'logoUrl'
|
||
| 'primaryColor'
|
||
| 'avatarUrl'
|
||
| 'name'
|
||
| 'title'
|
||
| 'slogan'
|
||
| 'phone'
|
||
| 'email'
|
||
| 'whatsapp'
|
||
| 'linkedin'
|
||
| 'telegram'
|
||
| 'twitter'
|
||
| 'wechat';
|
||
|
||
export type BrandKitLockSource = {
|
||
logoUrl?: string;
|
||
primaryColor?: string;
|
||
lockedFields?: string[] | null;
|
||
whitelist?: string[] | null;
|
||
editableFields?: string[] | null;
|
||
fieldWhitelist?: string[] | null;
|
||
};
|
||
|
||
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;
|
||
timezone?: string;
|
||
lockedFields?: string[] | null;
|
||
};
|
||
|
||
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;
|
||
timezone?: 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;
|
||
code?: number;
|
||
card: CardProfile | null;
|
||
errorMessage: string;
|
||
};
|
||
|
||
export type CardEditPageOptions = {
|
||
id?: string;
|
||
};
|
||
|
||
export type CardEditState = {
|
||
rawId: string;
|
||
cardId: number | null;
|
||
name: string;
|
||
title: string;
|
||
company: string;
|
||
slogan: string;
|
||
avatarUrl: string;
|
||
phone: string;
|
||
email: string;
|
||
whatsapp: string;
|
||
linkedin: string;
|
||
timezone: string;
|
||
moduleOrder: string[];
|
||
lockedFields: 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 === 'slogan') {
|
||
return zh ? '口号' : 'Slogan';
|
||
}
|
||
if (key === 'phone') {
|
||
return zh ? '电话' : 'Phone';
|
||
}
|
||
if (key === 'email') {
|
||
return zh ? '邮箱' : 'Email';
|
||
}
|
||
if (key === 'timezone') {
|
||
return zh ? '时区' : 'Timezone';
|
||
}
|
||
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';
|
||
}
|
||
if (key === 'lockedHint') {
|
||
return zh ? '品牌规范已锁定,不可修改' : 'Locked by brand kit';
|
||
}
|
||
if (key === 'moduleOrderSection') {
|
||
return zh ? '模块顺序' : 'Module order';
|
||
}
|
||
if (key === 'moveUp') {
|
||
return zh ? '上移' : 'Move up';
|
||
}
|
||
if (key === 'moveDown') {
|
||
return zh ? '下移' : 'Move down';
|
||
}
|
||
return '';
|
||
}
|
||
|
||
function isKnownModuleKey(value: string): boolean {
|
||
return (
|
||
value === 'basic' || value === 'social' || value === 'privacy'
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 归一化模块顺序:去重、仅保留已知键,缺项按默认补齐
|
||
*/
|
||
export function normalizeModuleOrder(raw: unknown): string[] {
|
||
const seen: Record<string, boolean> = {};
|
||
const out: string[] = [];
|
||
if (Array.isArray(raw)) {
|
||
for (let i = 0; i < raw.length; i++) {
|
||
const item = raw[i];
|
||
if (typeof item !== 'string') {
|
||
continue;
|
||
}
|
||
const key = item.trim();
|
||
if (!isKnownModuleKey(key) || seen[key] === true) {
|
||
continue;
|
||
}
|
||
seen[key] = true;
|
||
out.push(key);
|
||
}
|
||
}
|
||
for (let i = 0; i < DEFAULT_MODULE_ORDER.length; i++) {
|
||
const key = DEFAULT_MODULE_ORDER[i];
|
||
if (seen[key] !== true) {
|
||
out.push(key);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
/**
|
||
* 上下移动模块。越界时原样返回。
|
||
*/
|
||
export function moveModuleInOrder(
|
||
order: string[],
|
||
index: number,
|
||
delta: number
|
||
): string[] {
|
||
const normalized = normalizeModuleOrder(order);
|
||
const target = index + delta;
|
||
if (index < 0 || index >= normalized.length) {
|
||
return normalized;
|
||
}
|
||
if (target < 0 || target >= normalized.length) {
|
||
return normalized;
|
||
}
|
||
const next: string[] = [];
|
||
for (let i = 0; i < normalized.length; i++) {
|
||
next.push(normalized[i]);
|
||
}
|
||
const tmp = next[index];
|
||
next[index] = next[target];
|
||
next[target] = tmp;
|
||
return next;
|
||
}
|
||
|
||
export function moduleOrderStorageKey(cardId: string | number): string {
|
||
return MODULE_ORDER_STORAGE_PREFIX + String(cardId);
|
||
}
|
||
|
||
export function readStoredModuleOrder(
|
||
cardId: string | number | null | undefined
|
||
): string[] {
|
||
if (cardId == null) {
|
||
return normalizeModuleOrder(null);
|
||
}
|
||
try {
|
||
if (typeof uni === 'undefined' || uni.getStorageSync == null) {
|
||
return normalizeModuleOrder(null);
|
||
}
|
||
const cached = uni.getStorageSync(moduleOrderStorageKey(cardId));
|
||
return normalizeModuleOrder(cached);
|
||
} catch (_e) {
|
||
return normalizeModuleOrder(null);
|
||
}
|
||
}
|
||
|
||
export function writeStoredModuleOrder(
|
||
cardId: string | number | null | undefined,
|
||
order: string[]
|
||
): void {
|
||
if (cardId == null) {
|
||
return;
|
||
}
|
||
try {
|
||
if (typeof uni === 'undefined' || uni.setStorageSync == null) {
|
||
return;
|
||
}
|
||
uni.setStorageSync(
|
||
moduleOrderStorageKey(cardId),
|
||
normalizeModuleOrder(order)
|
||
);
|
||
} catch (_e) {}
|
||
}
|
||
|
||
/**
|
||
* 解析为正整数。非正整数返回 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;
|
||
}
|
||
|
||
/** 品牌管控字段:whitelist 模式下未列入则锁定 */
|
||
const BRAND_CONTROL_FIELDS: string[] = ['company', 'logoUrl', 'primaryColor'];
|
||
|
||
function normalizeLockToken(raw: string): string {
|
||
return String(raw).trim();
|
||
}
|
||
|
||
function fieldAliasGroup(field: string): string[] {
|
||
const key = normalizeLockToken(field);
|
||
if (key === 'company' || key === 'companyName') {
|
||
return ['company', 'companyName'];
|
||
}
|
||
if (key === 'logo' || key === 'logoUrl') {
|
||
return ['logo', 'logoUrl'];
|
||
}
|
||
return [key];
|
||
}
|
||
|
||
function listIncludesField(
|
||
list: string[] | null | undefined,
|
||
field: string
|
||
): boolean {
|
||
if (list == null || list.length === 0) {
|
||
return false;
|
||
}
|
||
const aliases = fieldAliasGroup(field);
|
||
for (let i = 0; i < list.length; i++) {
|
||
const item = normalizeLockToken(list[i]);
|
||
if (item === '') {
|
||
continue;
|
||
}
|
||
for (let j = 0; j < aliases.length; j++) {
|
||
if (item === aliases[j]) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
function readStringArray(value: unknown): string[] | null {
|
||
if (value == null) {
|
||
return null;
|
||
}
|
||
if (!Array.isArray(value)) {
|
||
return null;
|
||
}
|
||
const out: string[] = [];
|
||
for (let i = 0; i < value.length; i++) {
|
||
const item = value[i];
|
||
if (typeof item === 'string') {
|
||
const text = item.trim();
|
||
if (text !== '') {
|
||
out.push(text);
|
||
}
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
function readWhitelist(kit: BrandKitLockSource | null | undefined): string[] | null {
|
||
if (kit == null) {
|
||
return null;
|
||
}
|
||
const fromWhitelist = readStringArray(kit.whitelist);
|
||
if (fromWhitelist != null) {
|
||
return fromWhitelist;
|
||
}
|
||
const fromEditable = readStringArray(kit.editableFields);
|
||
if (fromEditable != null) {
|
||
return fromEditable;
|
||
}
|
||
return readStringArray(kit.fieldWhitelist);
|
||
}
|
||
|
||
function deriveLocksFromWhitelist(whitelist: string[]): string[] {
|
||
const locked: string[] = [];
|
||
for (let i = 0; i < BRAND_CONTROL_FIELDS.length; i++) {
|
||
const field = BRAND_CONTROL_FIELDS[i];
|
||
if (!listIncludesField(whitelist, field)) {
|
||
locked.push(field);
|
||
}
|
||
}
|
||
return locked;
|
||
}
|
||
|
||
/**
|
||
* 从名片或品牌套件提取锁定字段。
|
||
* 优先 lockedFields;否则用 whitelist/editableFields 反推品牌管控字段。
|
||
* 无锁定数据返回 [],不因仅有 brandKit 资产而锁全部。
|
||
*/
|
||
export function extractLockedFields(
|
||
source:
|
||
| (CardProfile & {
|
||
lockedFields?: string[] | null;
|
||
brandKit?: BrandKitLockSource | null;
|
||
})
|
||
| BrandKitLockSource
|
||
| null
|
||
| undefined
|
||
): string[] {
|
||
if (source == null) {
|
||
return [];
|
||
}
|
||
|
||
const asRecord = source as {
|
||
lockedFields?: string[] | null;
|
||
brandKit?: BrandKitLockSource | null;
|
||
whitelist?: string[] | null;
|
||
editableFields?: string[] | null;
|
||
fieldWhitelist?: string[] | null;
|
||
};
|
||
|
||
const topLocked = readStringArray(asRecord.lockedFields);
|
||
if (topLocked != null) {
|
||
return topLocked;
|
||
}
|
||
|
||
const kit =
|
||
asRecord.brandKit != null
|
||
? asRecord.brandKit
|
||
: (source as BrandKitLockSource);
|
||
|
||
if (kit != null) {
|
||
const kitLocked = readStringArray(kit.lockedFields);
|
||
if (kitLocked != null) {
|
||
return kitLocked;
|
||
}
|
||
const whitelist = readWhitelist(kit);
|
||
if (whitelist != null && whitelist.length > 0) {
|
||
return deriveLocksFromWhitelist(whitelist);
|
||
}
|
||
}
|
||
|
||
return [];
|
||
}
|
||
|
||
/**
|
||
* 判断表单字段是否被品牌套件锁定(含 company/companyName、logo/logoUrl 别名)
|
||
*/
|
||
export function isFieldLocked(
|
||
lockedFields: string[] | null | undefined,
|
||
field: string
|
||
): boolean {
|
||
return listIncludesField(lockedFields, field);
|
||
}
|
||
|
||
function assignFilled(
|
||
payload: CardUpdatePayload,
|
||
key:
|
||
| 'name'
|
||
| 'title'
|
||
| 'company'
|
||
| 'slogan'
|
||
| 'avatarUrl'
|
||
| 'phone'
|
||
| 'email'
|
||
| 'whatsapp'
|
||
| 'linkedin'
|
||
| 'telegram'
|
||
| 'twitter'
|
||
| 'wechat'
|
||
| 'timezone',
|
||
value: string | null | undefined,
|
||
lockedFields?: string[] | null
|
||
): void {
|
||
if (isFieldLocked(lockedFields, key)) {
|
||
return;
|
||
}
|
||
const filled = pickFilled(value);
|
||
if (filled != null) {
|
||
payload[key] = filled;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 只带已填字段与解析后的 id。缺 id 返回 null。
|
||
* 锁定字段(含 companyName→company 别名)不进入 payload。
|
||
*/
|
||
export function buildUpdatePayload(
|
||
form: CardEditForm,
|
||
lockedFields?: string[] | null
|
||
): CardUpdatePayload | null {
|
||
const id = parseCardId(form.id);
|
||
if (id == null) {
|
||
return null;
|
||
}
|
||
const locks =
|
||
lockedFields != null
|
||
? lockedFields
|
||
: form.lockedFields != null
|
||
? form.lockedFields
|
||
: [];
|
||
const payload: CardUpdatePayload = { id };
|
||
assignFilled(payload, 'name', form.name, locks);
|
||
assignFilled(payload, 'title', form.title, locks);
|
||
assignFilled(payload, 'company', form.company, locks);
|
||
assignFilled(payload, 'slogan', form.slogan, locks);
|
||
assignFilled(payload, 'avatarUrl', form.avatarUrl, locks);
|
||
assignFilled(payload, 'phone', form.phone, locks);
|
||
assignFilled(payload, 'email', form.email, locks);
|
||
assignFilled(payload, 'whatsapp', form.whatsapp, locks);
|
||
assignFilled(payload, 'linkedin', form.linkedin, locks);
|
||
assignFilled(payload, 'telegram', form.telegram, locks);
|
||
assignFilled(payload, 'twitter', form.twitter, locks);
|
||
assignFilled(payload, 'wechat', form.wechat, locks);
|
||
assignFilled(payload, 'timezone', form.timezone, locks);
|
||
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 不请求。
|
||
* lockedFields 可显式传入,否则用 form.lockedFields。
|
||
*/
|
||
export async function saveCard(
|
||
form: CardEditForm,
|
||
api?: SaveCardApi,
|
||
lockedFields?: string[] | null
|
||
): Promise<SaveCardResult> {
|
||
const payload = buildUpdatePayload(form, lockedFields);
|
||
if (payload == null) {
|
||
return {
|
||
ok: false,
|
||
code: undefined,
|
||
card: null,
|
||
errorMessage: '',
|
||
};
|
||
}
|
||
|
||
const update =
|
||
api != null && api.updateOwnerCard != null
|
||
? api.updateOwnerCard
|
||
: updateOwnerCard;
|
||
|
||
try {
|
||
const card = await update(asUpdatePayload(payload));
|
||
return {
|
||
ok: true,
|
||
// Cool-Admin:仅 code === 1000 视为更新成功(http 层已解包)
|
||
code: 1000,
|
||
card,
|
||
errorMessage: '',
|
||
};
|
||
} catch (err) {
|
||
return {
|
||
ok: false,
|
||
code: undefined,
|
||
card: null,
|
||
errorMessage: readErrorMessage(err),
|
||
};
|
||
}
|
||
}
|
||
|
||
export function createCardEditState(): CardEditState {
|
||
return {
|
||
rawId: '',
|
||
cardId: null,
|
||
name: '',
|
||
title: '',
|
||
company: '',
|
||
slogan: '',
|
||
avatarUrl: '',
|
||
phone: '',
|
||
email: '',
|
||
whatsapp: '',
|
||
linkedin: '',
|
||
timezone: '',
|
||
moduleOrder: normalizeModuleOrder(null),
|
||
lockedFields: [],
|
||
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;
|
||
if (parsed != null) {
|
||
state.moduleOrder = readStoredModuleOrder(parsed);
|
||
}
|
||
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.slogan = readOptionalText(card.slogan);
|
||
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);
|
||
state.timezone = readOptionalText(card.timezone);
|
||
state.lockedFields = extractLockedFields(
|
||
card as CardProfile & {
|
||
lockedFields?: string[] | null;
|
||
brandKit?: BrandKitLockSource | null;
|
||
}
|
||
);
|
||
}
|
||
|
||
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.lockedFields = [];
|
||
}
|
||
state.errorMessage = result.errorMessage;
|
||
}
|
||
|
||
export function formFromState(state: CardEditState): CardEditForm {
|
||
return {
|
||
id: state.cardId,
|
||
name: state.name,
|
||
title: state.title,
|
||
company: state.company,
|
||
slogan: state.slogan,
|
||
avatarUrl: state.avatarUrl,
|
||
phone: state.phone,
|
||
email: state.email,
|
||
whatsapp: state.whatsapp,
|
||
linkedin: state.linkedin,
|
||
timezone: state.timezone,
|
||
lockedFields: state.lockedFields,
|
||
};
|
||
}
|
||
|
||
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,
|
||
state.lockedFields
|
||
);
|
||
// 仅接口 code === 1000 视为保存成功
|
||
if (result.ok && result.code === 1000 && result.card != null) {
|
||
applyCardToState(state, result.card);
|
||
writeStoredModuleOrder(state.cardId, state.moduleOrder);
|
||
state.saving = false;
|
||
return true;
|
||
}
|
||
state.saving = false;
|
||
state.errorMessage = result.errorMessage;
|
||
return false;
|
||
}
|
||
|
||
export function goBackFromCardEdit(): void {
|
||
uni.navigateBack({
|
||
delta: 1,
|
||
fail: () => {
|
||
uni.switchTab({
|
||
url: '/pages/owner/card/index',
|
||
});
|
||
},
|
||
});
|
||
}
|