- [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 打包无错误
262 行
5.3 KiB
Plaintext
262 行
5.3 KiB
Plaintext
/**
|
|
* OmniCard AI (CRMmp) - 用户端跨端核心数据契约与 TypeScript / UTS 类型体系
|
|
* 严格遵循 AGENTS.md 规范与出海 SaaS 契约,与 backend / admin 镜像对齐
|
|
*/
|
|
|
|
export type CardStatus = 'draft' | 'published' | 'archived';
|
|
|
|
export interface CardProfile {
|
|
id?: string | number;
|
|
tenantId?: string | number;
|
|
userId?: 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;
|
|
audioPronunciationUrl?: string;
|
|
audioPronounceUrl?: string;
|
|
status?: CardStatus;
|
|
themeCode?: string;
|
|
url?: string;
|
|
address?: string;
|
|
city?: string;
|
|
country?: string;
|
|
[key: string]: any;
|
|
}
|
|
|
|
export type BentoBlockType = 'hero' | 'case' | 'article' | 'social' | 'video' | 'form';
|
|
|
|
export type BentoColSpan = 1 | 2;
|
|
|
|
export type BentoRowSpan = 1 | 2;
|
|
|
|
export interface BentoBlockPublic {
|
|
id: number | string;
|
|
cardId: number | string;
|
|
type: BentoBlockType;
|
|
colSpan: BentoColSpan;
|
|
rowSpan: BentoRowSpan;
|
|
sortOrder: number;
|
|
title?: string;
|
|
content?: string;
|
|
mediaUrl?: string;
|
|
linkUrl?: string;
|
|
metaJson?: string | Record<string, any>;
|
|
}
|
|
|
|
export interface BrandKit {
|
|
logoUrl: string;
|
|
primaryColor: string;
|
|
}
|
|
|
|
export interface CardPortalDetail {
|
|
id: number | string;
|
|
slug?: string;
|
|
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;
|
|
audioPronunciationUrl?: string;
|
|
audioPronounceUrl?: string;
|
|
themeCode?: string;
|
|
status: CardStatus;
|
|
bentoBlocks: BentoBlockPublic[];
|
|
brandKit?: BrandKit;
|
|
cachedAt?: number;
|
|
}
|
|
|
|
export type LeadStatus = 'new' | 'contacted' | 'hot' | 'won' | 'archived';
|
|
|
|
export type LeadSource = 'nfc' | 'qr' | 'link' | 'ai_chat' | 'exchange';
|
|
|
|
export type CardContactSource = 'exchange' | 'collect' | 'scan';
|
|
|
|
export interface CardContact {
|
|
id?: string | number;
|
|
ownerUserId: number;
|
|
ownerCardId?: number | null;
|
|
peerCardId: number;
|
|
source: CardContactSource;
|
|
leadId?: number | null;
|
|
peerName?: string;
|
|
peerTitle?: string;
|
|
peerCompany?: string;
|
|
peerAvatarUrl?: string;
|
|
peerSlug?: string;
|
|
}
|
|
|
|
export interface LeadEntity {
|
|
id: string | number;
|
|
tenantId?: string | number;
|
|
cardId: string | number;
|
|
visitorId?: string;
|
|
name: string;
|
|
company?: string;
|
|
phone?: string;
|
|
email?: string;
|
|
status: LeadStatus;
|
|
intentScore: number;
|
|
source: LeadSource;
|
|
notes?: string;
|
|
}
|
|
|
|
export interface SubmitLeadParams {
|
|
cardId: string | number;
|
|
name: string;
|
|
company?: string;
|
|
phone?: string;
|
|
email?: string;
|
|
source?: string;
|
|
notes?: string;
|
|
visitorId?: string;
|
|
[key: string]: any;
|
|
}
|
|
|
|
export type TouchType = 'nfc' | 'qr' | 'link';
|
|
|
|
export interface RadarTrack {
|
|
id: string | number;
|
|
tenantId?: string | number;
|
|
cardId: string | number;
|
|
visitorId: string;
|
|
ip?: string;
|
|
country?: string;
|
|
city?: string;
|
|
touchType: TouchType;
|
|
stayDurationMs: number;
|
|
scrollDepth: number;
|
|
viewedBlocks: string[];
|
|
createdAt?: string | Date;
|
|
}
|
|
|
|
export interface RecordRadarTrackParams {
|
|
cardId: string | number;
|
|
visitorId: string;
|
|
touchType?: string;
|
|
stayDurationMs?: number;
|
|
scrollDepth?: number;
|
|
viewedBlocks?: string[];
|
|
ip?: string;
|
|
country?: string;
|
|
city?: string;
|
|
[key: string]: any;
|
|
}
|
|
|
|
export interface ApiResponse<T> {
|
|
code: number;
|
|
message: string;
|
|
data: T;
|
|
}
|
|
|
|
export type NFCMaterial = 'wood' | 'metal' | 'pvc';
|
|
export type NFCStatus = 'active' | 'suspended' | 'unbound';
|
|
export type NFCSceneMode = 'business' | 'social';
|
|
|
|
export interface NFCRecord {
|
|
id: string | number;
|
|
uid: string;
|
|
tenantId: string | number;
|
|
cardId?: string | number | null;
|
|
batchNo: string;
|
|
material: NFCMaterial;
|
|
status: NFCStatus;
|
|
sceneMode: NFCSceneMode;
|
|
lastScannedAt?: string | Date | null;
|
|
}
|
|
|
|
export interface NfcBindParams {
|
|
uid: string;
|
|
cardId: number;
|
|
}
|
|
|
|
export interface NfcUidParams {
|
|
uid: string;
|
|
}
|
|
|
|
export interface OwnerPageQuery {
|
|
page: number;
|
|
size: number;
|
|
keyWord?: string;
|
|
status?: LeadStatus;
|
|
cardId?: string | number;
|
|
visitorId?: string;
|
|
}
|
|
|
|
export interface QuotaCheckResult {
|
|
used: number;
|
|
limit: number;
|
|
remaining: number;
|
|
percentage: number;
|
|
warning: boolean;
|
|
exceeded: boolean;
|
|
plan?: string;
|
|
}
|
|
|
|
export interface OwnerPageData<T> {
|
|
list: T[];
|
|
pagination: {
|
|
page: number;
|
|
size: number;
|
|
total: number;
|
|
};
|
|
}
|
|
|
|
export type AiChatRole = 'user' | 'assistant' | 'system';
|
|
|
|
export type ChatMessageType = 'text' | 'lead_card';
|
|
|
|
export interface ChatLeadPayload {
|
|
title: string;
|
|
description: string;
|
|
name: string;
|
|
contact: string;
|
|
submitted: boolean;
|
|
submitting?: boolean;
|
|
successMessage?: string;
|
|
errorMessage?: string;
|
|
}
|
|
|
|
export interface ChatMessage {
|
|
role: AiChatRole;
|
|
content: string;
|
|
type?: ChatMessageType;
|
|
cardPayload?: ChatLeadPayload;
|
|
}
|
|
|
|
export interface AiChatMessage {
|
|
role: AiChatRole;
|
|
content: string;
|
|
type?: ChatMessageType;
|
|
cardPayload?: ChatLeadPayload;
|
|
}
|
|
|
|
export interface AiChatRequest {
|
|
cardId: string | number;
|
|
locale?: string;
|
|
messages: AiChatMessage[];
|
|
}
|
|
|
|
export interface AiChatReply {
|
|
reply: string;
|
|
locale: string;
|
|
shouldCollectLead: boolean;
|
|
}
|