- [P0安全底座] 多租户数据闭环:公开留资与雷达足迹自动逆向追溯绑定名片 tenantId 与归属销售 userId - [P0性能稳定] 消除内存全量加载:公海池分批带条件扫描回收,雷达国家分布改用 SQL 原生 GROUP BY 聚合查询彻底消除 OOM 隐患 - [P0商业变现] 商业化配额门禁切面:在 Cool-Admin modifyBefore 生命周期织入 assertQuotaAvailable 校验,阻断超额名片创建 - [P0环境解耦] 客户端解耦本地硬编码:request.uts 支持 api_base_url 本地覆盖,生产数据库名修正为 crmmpdata - [P1高并发削峰] 雷达足迹批量缓冲引擎:引入 CrmRadarBufferService 环形缓冲队列与阈值/定时批量 Bulk Insert 落盘 - [P2中枢租户化] AI 智能中枢多租户 BYOK 与 Token 成本风控:支持企业自带 Key 隔离、Token 消耗账本、用量配额门禁与名片人设个性化联动 - [跨端规范] 对齐 Uni-app x 移动微官网原生首屏与底部原生 tabBar 规范 - [测试驱动] 全工程 116 个测试套件,1,175 个单元与集成测试用例全部 100% 绿色通过,管理后台 Vite 打包无错误
407 行
11 KiB
Plaintext
407 行
11 KiB
Plaintext
/**
|
|
* OmniCard AI (CRMmp) - 名片主个人中心与消息通知配置业务驱动层
|
|
* 严格按照 AGENTS.md 规范与 uts 强类型约束
|
|
*/
|
|
|
|
import {
|
|
OmniLocale,
|
|
PORTAL_LOCALE_STORAGE_KEY,
|
|
getCurrentLocale,
|
|
setAppLocale,
|
|
getOmniText,
|
|
} from '../../../common/locale.uts';
|
|
|
|
export { PORTAL_LOCALE_STORAGE_KEY };
|
|
|
|
export type UserProfileAccount = {
|
|
name: string;
|
|
company: string;
|
|
department: string;
|
|
avatar: string;
|
|
};
|
|
|
|
export type NotificationSettings = {
|
|
radarPush: boolean;
|
|
leadNotice: boolean;
|
|
};
|
|
|
|
export type ProfileState = {
|
|
account: UserProfileAccount;
|
|
notifications: NotificationSettings;
|
|
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';
|
|
|
|
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: 'Alex Vance',
|
|
company: 'Nexus Tech Global',
|
|
department: 'Overseas Business Dept',
|
|
avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=256&q=80',
|
|
};
|
|
}
|
|
|
|
export function getDefaultNotifications(): NotificationSettings {
|
|
return {
|
|
radarPush: true,
|
|
leadNotice: true,
|
|
};
|
|
}
|
|
|
|
export function createProfileState(locale?: OmniLocale): ProfileState {
|
|
const currentLocale = locale != null && locale !== '' ? locale : getCurrentLocale();
|
|
return {
|
|
account: getDefaultAccount(),
|
|
notifications: getDefaultNotifications(),
|
|
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 defaultAcc = getDefaultAccount();
|
|
const safeName = typeof parsedAccount.name === 'string' && parsedAccount.name.trim() !== ''
|
|
? parsedAccount.name.trim()
|
|
: defaultAcc.name;
|
|
const safeCompany = typeof parsedAccount.company === 'string' && parsedAccount.company.trim() !== ''
|
|
? parsedAccount.company.trim()
|
|
: defaultAcc.company;
|
|
const safeDept = typeof parsedAccount.department === 'string' && parsedAccount.department.trim() !== ''
|
|
? parsedAccount.department.trim()
|
|
: defaultAcc.department;
|
|
const safeAvatar = typeof parsedAccount.avatar === 'string' && parsedAccount.avatar.trim() !== ''
|
|
? parsedAccount.avatar.trim()
|
|
: defaultAcc.avatar;
|
|
|
|
state.account = {
|
|
name: safeName,
|
|
company: safeCompany,
|
|
department: safeDept,
|
|
avatar: safeAvatar,
|
|
};
|
|
}
|
|
} 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();
|
|
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)
|
|
);
|
|
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 = '';
|
|
}
|
|
|
|
/**
|
|
* 瑞士国际主义多语言文案解析器,无 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');
|
|
}
|
|
|
|
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 '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 'workspaceSection':
|
|
return isZh ? '工作台' : 'Workspace';
|
|
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.';
|
|
default:
|
|
return key;
|
|
}
|
|
}
|