文件
OmniCard/mpuniapp/pages/owner/profile/useProfile.uts
wikiboss 62e0ae4435 feat: 同步管理端、用户端与后端业务,并接通线上 Web 接口
补齐平台订阅、外部登录、CRM 同步与用户端页面;Nginx 转发用户端接口,登录页使用 Google 与 Apple 标志。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 18:01:47 +08:00

662 行
19 KiB
Plaintext

/**
* OmniCard AI (CRMmp) - 名片主个人中心与消息通知配置业务驱动层
* 严格按照 AGENTS.md 规范与 uts 强类型约束
*/
import {
OmniLocale,
PORTAL_LOCALE_STORAGE_KEY,
getCurrentLocale,
setAppLocale,
getOmniText,
} from '../../../common/locale.uts';
import {
CardProfile,
OwnerPageData,
OwnerPageQuery,
} from '../../../common/types.uts';
import { fetchOwnerCardPage } from '../../../common/api.uts';
export { PORTAL_LOCALE_STORAGE_KEY };
export type UserProfileAccount = {
name: string;
company: string;
department: string;
avatar: string;
cardId: string;
cardSlug: string;
};
export type NotificationSettings = {
radarPush: boolean;
leadNotice: boolean;
};
export type ProfileMetrics = {
cardsCount: number;
visitorsCount: number;
leadsCount: number;
};
export type ProfileState = {
account: UserProfileAccount;
notifications: NotificationSettings;
metrics: ProfileMetrics;
locale: OmniLocale;
saving: boolean;
loading: boolean;
errorMessage: string;
};
export type StorageApi = {
getStorageSync: (key: string) => unknown;
setStorageSync: (key: string, value: unknown) => void;
};
export const PROFILE_ACCOUNT_STORAGE_KEY = 'omni_owner_profile_account';
export const PROFILE_NOTIFICATIONS_STORAGE_KEY = 'omni_owner_profile_notifications';
export const PROFILE_METRICS_STORAGE_KEY = 'omni_owner_profile_metrics';
function defaultStorage(): StorageApi {
return {
getStorageSync: (key: string): unknown => {
try {
if (typeof uni !== 'undefined' && uni.getStorageSync != null) {
return uni.getStorageSync(key);
}
} catch (_e) {}
return '';
},
setStorageSync: (key: string, value: unknown): void => {
try {
if (typeof uni !== 'undefined' && uni.setStorageSync != null) {
uni.setStorageSync(key, value);
}
} catch (err: unknown) {
throw err;
}
},
};
}
export function getDefaultAccount(): UserProfileAccount {
return {
name: '',
company: '',
department: '',
avatar: '',
cardId: '',
cardSlug: '',
};
}
export type LoadProfileAccountApi = {
fetchOwnerCardPage: (
query: OwnerPageQuery
) => Promise<OwnerPageData<CardProfile>>;
};
/**
* 读取接口可选组织字段:仅 trim,缺失或空白一律空串
*/
export function readOptionalOrgText(value: string | null | undefined): string {
if (value == null) {
return '';
}
return String(value).trim();
}
/**
* 公司/部门展示:有值原样返回,缺失或空串走 i18n 空态
*/
export function displayOrgField(value: string | null | undefined): string {
const text = readOptionalOrgText(value);
if (text === '') {
return resolveProfileText('empty');
}
return text;
}
/**
* 接口失败或无数据时清空公司与部门,禁止保留上次/示例值
*/
export function clearOrgFields(state: ProfileState): void {
state.account.company = '';
state.account.department = '';
}
function readDepartmentFromCard(card: CardProfile): string {
const extra = card as CardProfile & { department?: string | null };
return readOptionalOrgText(extra.department);
}
/**
* 从名片分页接口拉取当前用户公司/部门(仅使用接口返回字段)
*/
export async function loadOwnerProfileAccount(
state: ProfileState,
api?: LoadProfileAccountApi
): Promise<boolean> {
const fetchPage =
api != null ? api.fetchOwnerCardPage : fetchOwnerCardPage;
state.loading = true;
try {
const data = await fetchPage({ page: 1, size: 1 } as OwnerPageQuery);
const list = data.list != null ? data.list : [];
if (list.length === 0) {
clearOrgFields(state);
state.account.cardId = '';
state.account.cardSlug = '';
state.errorMessage = '';
state.loading = false;
return true;
}
const card = list[0];
state.account.company = readOptionalOrgText(card.company);
state.account.department = readDepartmentFromCard(card);
state.account.cardId = card.id != null ? String(card.id) : '';
const slugText = card.slug != null ? String(card.slug).trim() : '';
state.account.cardSlug = slugText;
if (card.name != null && String(card.name).trim() !== '') {
state.account.name = String(card.name).trim();
}
if (card.avatarUrl != null && String(card.avatarUrl).trim() !== '') {
state.account.avatar = String(card.avatarUrl).trim();
}
state.errorMessage = '';
state.loading = false;
return true;
} catch (err: unknown) {
clearOrgFields(state);
state.account.cardId = '';
state.account.cardSlug = '';
const msg = err instanceof Error && err.message !== ''
? err.message
: resolveProfileText('error');
state.errorMessage = msg;
state.loading = false;
return false;
}
}
export function getDefaultNotifications(): NotificationSettings {
return {
radarPush: true,
leadNotice: true,
};
}
export function getDefaultMetrics(): ProfileMetrics {
return {
cardsCount: 0,
visitorsCount: 0,
leadsCount: 0,
};
}
export function loadProfileMetrics(storage?: StorageApi): ProfileMetrics {
const store = storage != null ? storage : defaultStorage();
try {
const raw = store.getStorageSync(PROFILE_METRICS_STORAGE_KEY);
const parsed = safeParseJson<Record<string, unknown>>(raw);
if (parsed != null && typeof parsed === 'object') {
const defaultM = getDefaultMetrics();
const cards = typeof parsed.cardsCount === 'number' ? parsed.cardsCount : defaultM.cardsCount;
const visitors = typeof parsed.visitorsCount === 'number' ? parsed.visitorsCount : defaultM.visitorsCount;
const leads = typeof parsed.leadsCount === 'number' ? parsed.leadsCount : defaultM.leadsCount;
return {
cardsCount: cards,
visitorsCount: visitors,
leadsCount: leads,
};
}
} catch (_e) {}
return getDefaultMetrics();
}
export function saveProfileMetrics(metrics: ProfileMetrics, storage?: StorageApi): boolean {
const store = storage != null ? storage : defaultStorage();
try {
store.setStorageSync(PROFILE_METRICS_STORAGE_KEY, JSON.stringify(metrics));
return true;
} catch (_e) {
return false;
}
}
export function createProfileState(locale?: OmniLocale): ProfileState {
const currentLocale = locale != null ? locale : getCurrentLocale();
return {
account: getDefaultAccount(),
notifications: getDefaultNotifications(),
metrics: getDefaultMetrics(),
locale: currentLocale,
saving: false,
loading: false,
errorMessage: '',
};
}
function safeParseJson<T>(raw: unknown): T | null {
if (raw == null || typeof raw !== 'string') {
return null;
}
const trimmed = raw.trim();
if (trimmed === '' || (!trimmed.startsWith('{') && !trimmed.startsWith('['))) {
return null;
}
try {
return JSON.parse(trimmed) as T;
} catch (_e) {
return null;
}
}
/**
* 从本地存储加载个人名片主资料与通知设置,具备完整防御性容错
*/
export function loadProfileSettings(storage?: StorageApi): ProfileState {
const store = storage != null ? storage : defaultStorage();
const state = createProfileState();
try {
const rawAccount = store.getStorageSync(PROFILE_ACCOUNT_STORAGE_KEY);
const parsedAccount = safeParseJson<Record<string, unknown>>(rawAccount);
if (parsedAccount != null && typeof parsedAccount === 'object') {
const safeName = typeof parsedAccount.name === 'string'
? parsedAccount.name.trim()
: '';
const safeCompany = typeof parsedAccount.company === 'string'
? parsedAccount.company.trim()
: '';
const safeDept = typeof parsedAccount.department === 'string'
? parsedAccount.department.trim()
: '';
const safeAvatar = typeof parsedAccount.avatar === 'string'
? parsedAccount.avatar.trim()
: '';
const safeCardId = typeof parsedAccount.cardId === 'string'
? parsedAccount.cardId.trim()
: '';
const safeCardSlug = typeof parsedAccount.cardSlug === 'string'
? parsedAccount.cardSlug.trim()
: '';
state.account = {
name: safeName,
company: safeCompany,
department: safeDept,
avatar: safeAvatar,
cardId: safeCardId,
cardSlug: safeCardSlug,
};
}
} catch (_e) {
// 降级使用默认账户
state.account = getDefaultAccount();
}
try {
const rawNotice = store.getStorageSync(PROFILE_NOTIFICATIONS_STORAGE_KEY);
const parsedNotice = safeParseJson<Record<string, unknown>>(rawNotice);
if (parsedNotice != null && typeof parsedNotice === 'object') {
const defaultNotif = getDefaultNotifications();
state.notifications = {
radarPush: typeof parsedNotice.radarPush === 'boolean'
? parsedNotice.radarPush
: defaultNotif.radarPush,
leadNotice: typeof parsedNotice.leadNotice === 'boolean'
? parsedNotice.leadNotice
: defaultNotif.leadNotice,
};
}
} catch (_e) {
// 降级使用默认通知
state.notifications = getDefaultNotifications();
}
state.locale = getCurrentLocale();
state.metrics = loadProfileMetrics(store);
return state;
}
/**
* 保存名片主资料与通知设置到本地存储
*/
export function saveProfileSettings(state: ProfileState, storage?: StorageApi): boolean {
const store = storage != null ? storage : defaultStorage();
try {
store.setStorageSync(
PROFILE_ACCOUNT_STORAGE_KEY,
JSON.stringify(state.account)
);
store.setStorageSync(
PROFILE_NOTIFICATIONS_STORAGE_KEY,
JSON.stringify(state.notifications)
);
saveProfileMetrics(state.metrics, store);
state.errorMessage = '';
return true;
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : 'Storage save failed';
state.errorMessage = msg;
return false;
}
}
/**
* 更新账户资料字段,具备非空与空格修剪防御保护
*/
export function updateAccount(
state: ProfileState,
patch: Partial<UserProfileAccount>
): void {
if (patch == null) {
return;
}
if (typeof patch.name === 'string' && patch.name.trim() !== '') {
state.account.name = patch.name.trim();
}
if (typeof patch.company === 'string' && patch.company.trim() !== '') {
state.account.company = patch.company.trim();
}
if (typeof patch.department === 'string' && patch.department.trim() !== '') {
state.account.department = patch.department.trim();
}
if (typeof patch.avatar === 'string' && patch.avatar.trim() !== '') {
state.account.avatar = patch.avatar.trim();
}
}
/**
* 切换雷达即时推送通知开关,并同步记忆持久化
*/
export function toggleRadarPush(
state: ProfileState,
value?: boolean,
storage?: StorageApi
): boolean {
const store = storage != null ? storage : defaultStorage();
const nextVal = value !== undefined ? Boolean(value) : !state.notifications.radarPush;
state.notifications.radarPush = nextVal;
try {
store.setStorageSync(
PROFILE_NOTIFICATIONS_STORAGE_KEY,
JSON.stringify(state.notifications)
);
state.errorMessage = '';
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : 'Failed to persist radar push';
state.errorMessage = msg;
}
return nextVal;
}
/**
* 切换新线索提醒开关,并同步记忆持久化
*/
export function toggleLeadNotice(
state: ProfileState,
value?: boolean,
storage?: StorageApi
): boolean {
const store = storage != null ? storage : defaultStorage();
const nextVal = value !== undefined ? Boolean(value) : !state.notifications.leadNotice;
state.notifications.leadNotice = nextVal;
try {
store.setStorageSync(
PROFILE_NOTIFICATIONS_STORAGE_KEY,
JSON.stringify(state.notifications)
);
state.errorMessage = '';
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : 'Failed to persist lead notice';
state.errorMessage = msg;
}
return nextVal;
}
/**
* 切换首选语言并记忆持久化
*/
export function setPreferredLocale(
state: ProfileState,
locale: string,
storage?: StorageApi
): OmniLocale {
const safeLocales: OmniLocale[] = [
'zh-CN',
'en-US',
'ar-SA',
'es-ES',
'de-DE',
'fr-FR',
'ja-JP',
];
let targetLocale: OmniLocale = 'zh-CN';
if (locale != null) {
const found = safeLocales.find((l: OmniLocale) => l === locale);
if (found != null) {
targetLocale = found;
} else {
const lower = locale.toLowerCase();
if (lower.startsWith('en')) {
targetLocale = 'en-US';
}
}
}
state.locale = targetLocale;
setAppLocale(targetLocale);
if (storage != null) {
try {
storage.setStorageSync(PORTAL_LOCALE_STORAGE_KEY, targetLocale);
} catch (_e) {}
}
return targetLocale;
}
/**
* 预览个人头像
*/
export function previewAccountAvatar(state: ProfileState): void {
if (state.account.avatar !== '') {
// #ifndef UNI-APP-X-TEST
try {
if (typeof uni !== 'undefined' && typeof uni.previewImage === 'function') {
uni.previewImage({
urls: [state.account.avatar],
current: state.account.avatar,
});
}
} catch (_e) {}
// #endif
}
}
/**
* 移除个人头像
*/
export function removeAccountAvatar(state: ProfileState): void {
state.account.avatar = '';
}
/**
* 计算配额使用百分比 (0-100),防溢出与除零防御
*/
export function calculateQuotaPercent(used: number, limit: number): number {
if (limit <= 0) {
return 100;
}
if (used <= 0) {
return 0;
}
const pct = Math.round((used / limit) * 100);
if (pct > 100) {
return 100;
}
return pct;
}
/**
* 瑞士国际主义多语言文案解析器,无 emoji
*/
export function resolveProfileText(key: string): string {
const locale = getCurrentLocale();
if (key === 'save') {
return getOmniText(locale, 'common.save');
}
if (key === 'saved') {
return getOmniText(locale, 'common.success');
}
if (key === 'loading') {
return getOmniText(locale, 'common.loading');
}
if (key === 'error') {
return getOmniText(locale, 'common.error');
}
if (key === 'empty') {
return getOmniText(locale, 'common.empty');
}
const isZh = locale === 'zh-CN';
switch (key) {
case 'title':
return isZh ? '个人中心' : 'Profile';
case 'subtitle':
return isZh ? '管理名片主信息与系统通知' : 'Manage account and notifications';
case 'accountSection':
return isZh ? '基本资料' : 'Account';
case 'nameLabel':
return isZh ? '姓名' : 'Name';
case 'companyLabel':
return isZh ? '所属企业' : 'Company';
case 'departmentLabel':
return isZh ? '部门' : 'Department';
case 'avatarLabel':
return isZh ? '头像' : 'Avatar';
case 'tierPro':
return isZh ? '商业专业版' : 'Pro Plan';
case 'tierBadge':
return 'PRO';
case 'previewPortal':
return isZh ? '预览微官网' : 'Preview site';
case 'metricsCards':
return isZh ? '活跃名片' : 'Active Cards';
case 'metricsVisitors':
return isZh ? '雷达访客' : 'Radar Visitors';
case 'metricsLeads':
return isZh ? '沉淀线索' : 'Captured Leads';
case 'notificationsSection':
return isZh ? '消息通知' : 'Notifications';
case 'radarPushTitle':
return isZh ? '雷达即时推送' : 'Radar Push';
case 'radarPushDesc':
return isZh ? '国际访客访问名片时立即通知' : 'Instant alert when visitors view card';
case 'leadNoticeTitle':
return isZh ? '新线索提醒' : 'Lead Notice';
case 'leadNoticeDesc':
return isZh ? '客商留资或互换名片时实时触达' : 'Real-time notice for new leads';
case 'languageSection':
return isZh ? '偏好设置' : 'Preferences';
case 'languageLabel':
return isZh ? '界面语言' : 'Display Language';
case 'logout':
return isZh ? '退出登录' : 'Log out';
case 'workspaceSection':
return isZh ? '设备与团队' : 'Hardware & Team';
case 'cardStudioSection':
return isZh ? '名片内容管理' : 'Digital Card Studio';
case 'myCards':
return isZh ? '我的数字名片' : 'Digital Cards';
case 'cardEdit':
return isZh ? '名片编辑' : 'Edit Card';
case 'productCases':
return isZh ? '产品相册与案例' : 'Portfolio & Cases';
case 'articleDynamics':
return isZh ? '文章与商务动态' : 'Insights & News';
case 'sharePoster':
return isZh ? '分享海报' : 'Share Poster';
case 'exchangeHall':
return isZh ? '互换大厅' : 'Exchange Hall';
case 'editProfile':
return isZh ? '编辑资料' : 'Edit';
case 'nfcSection':
return isZh ? 'NFC 设备' : 'NFC Fleet';
case 'nfcScan':
return isZh ? 'NFC 贴卡激活' : 'Activate NFC card';
case 'nfcMode':
return isZh ? 'NFC 场景切换' : 'NFC scene mode';
case 'nfcSecurity':
return isZh ? 'NFC 挂失' : 'Freeze NFC card';
case 'teamBriefing':
return isZh ? '展会数据' : 'Team briefing';
case 'quotaSection':
return isZh ? '套餐与配额' : 'Plan & Quota';
case 'quotaCards':
return isZh ? '名片用量' : 'Card usage';
case 'quotaUpgrade':
return isZh ? '已达上限,请升级套餐' : 'Limit reached. Upgrade to continue.';
case 'upgradeQuota':
return isZh ? '升级扩容' : 'Upgrade Plan';
case 'planActive':
return isZh ? '生效中' : 'Active';
case 'confirmLogoutTitle':
return isZh ? '确认退出' : 'Sign Out';
case 'confirmLogoutContent':
return isZh ? '确定要退出当前账号吗?' : 'Are you sure you want to sign out?';
case 'myCardsSub':
return isZh ? '查看和切换名片' : 'View and switch cards';
case 'cardEditSub':
return isZh ? '改姓名、职位和联系方式' : 'Name, role, and contact details';
case 'productCasesSub':
return isZh ? '名片上展示的案例' : 'Cases shown on your card';
case 'articleDynamicsSub':
return isZh ? '名片上展示的文章' : 'Articles shown on your card';
case 'sharePosterSub':
return isZh ? '全屏出示二维码' : 'Full-screen QR';
case 'exchangeHallSub':
return isZh ? '当面交换名片' : 'Exchange in person';
case 'nfcScanSub':
return isZh ? '把实体卡绑到名片' : 'Bind a physical card';
case 'nfcModeSub':
return isZh ? '更换这张卡打开的页面' : 'Choose what the card opens';
case 'nfcSecuritySub':
return isZh ? '卡片丢失时远程停用' : 'Freeze a lost card';
case 'teamBriefingSub':
return isZh ? '团队今天的获客' : "Today's team results";
case 'avatarActionSheetTitle':
return isZh ? '头像管理' : 'Avatar Options';
case 'avatarActionPreview':
return isZh ? '查看大图' : 'View Full Photo';
case 'avatarActionChoose':
return isZh ? '从相册更换头像' : 'Change from Photos';
case 'avatarActionRemove':
return isZh ? '移除当前头像' : 'Remove Avatar';
case 'aboutApp':
return isZh ? '关于 OmniCard' : 'About OmniCard';
case 'aboutBody':
return isZh ? '数字名片、微官网和访客雷达' : 'Digital cards, microsites, and visitor radar';
case 'confirm':
return isZh ? '知道了' : 'OK';
case 'appVersion':
return 'OmniCard';
case 'securitySafe':
return isZh ? '保护中' : 'Protected';
case 'livePulse':
return isZh ? '实时' : 'Live';
default:
return key;
}
}