fix(client): 修复极速名片编辑与潜客详情白屏,补齐战报空状态与AI回退逻辑

这个提交包含在:
wikiboss
2026-09-21 15:23:29 +08:00
父节点 51a2f37180
当前提交 99b73bb309
共有 7 个文件被更改,包括 908 次插入 和 171 次删除

查看文件

@@ -289,6 +289,7 @@ describe('OmniCard AI 名片主极速编辑页契约 (edit.uvue)', () => {
expect(combined.includes('fetchOwnerCardInfo') || combined.includes('loadCard')).toBe(
true
);
expect(logicSource.includes('export {') && logicSource.includes('fetchOwnerCardInfo')).toBe(true);
expect(combined.includes('buildUpdatePayload')).toBe(true);
expect(pageSource.includes('v-model="state.name"')).toBe(true);
expect(pageSource.includes('v-model="state.title"')).toBe(true);

查看文件

@@ -93,7 +93,12 @@ type UseLeadDetailModule = {
next: string | null | undefined,
attachments?: string[]
) => string;
parseNoteAttachments: (rawNote: string | null | undefined) => {
cleanText: string;
imageUrls: string[];
};
formatWhatsAppUrl: (phone: string | null | undefined) => string;
createLeadDetailState?: () => Record<string, any>;
generateAiIcebreaker: (
lead: LeadEntity | null,
tracks: RadarTrack[],
@@ -310,6 +315,57 @@ describe('OmniCard AI 名片主潜客详情业务逻辑 (useLeadDetail.uts)', ()
expect(state.saving).toBe(false);
expect(updateOwnerLeadStatus).toHaveBeenCalledTimes(1);
});
it('3.4 submitLeadNote 在 nextNote 为空但有 attachments 时仍可提交附件', async () => {
const { submitLeadNote } = loadUseLeadDetail() as UseLeadDetailModule & {
submitLeadNote: (
state: Record<string, any>,
api?: SaveNoteApi
) => Promise<boolean>;
};
const httpPost = jest.fn(
async (url: string, data: Record<string, string | number>) => {
expect(url).toBe('/admin/crm/lead/update');
expect(data.id).toBe(21);
expect(data.notes).toContain('https://cdn.example.com/card.png');
return buildLead({ notes: String(data.notes) });
}
);
const state: Record<string, any> = {
leadId: 21,
existingNotes: 'Initial meeting',
nextNote: '',
attachments: ['https://cdn.example.com/card.png'],
saving: false,
errorMessage: '',
};
const ok = await submitLeadNote(state, { httpPost });
expect(ok).toBe(true);
expect(state.existingNotes).toContain('https://cdn.example.com/card.png');
expect(state.attachments).toEqual([]);
expect(httpPost).toHaveBeenCalledTimes(1);
});
it('3.5 submitLeadNote 在 nextNote 为空且无 attachments 时返回 false', async () => {
const { submitLeadNote } = loadUseLeadDetail() as UseLeadDetailModule & {
submitLeadNote: (
state: Record<string, any>,
api?: SaveNoteApi
) => Promise<boolean>;
};
const httpPost = jest.fn();
const state: Record<string, any> = {
leadId: 21,
existingNotes: '',
nextNote: ' ',
attachments: [],
saving: false,
errorMessage: '',
};
const ok = await submitLeadNote(state, { httpPost });
expect(ok).toBe(false);
expect(httpPost).not.toHaveBeenCalled();
});
});
describe('4. resolveLeadDetailText 只增文案', () => {
@@ -327,10 +383,13 @@ describe('OmniCard AI 名片主潜客详情业务逻辑 (useLeadDetail.uts)', ()
expect(resolveLeadDetailText('whatsappAction').length).toBeGreaterThan(0);
expect(resolveLeadDetailText('saveContactAction').length).toBeGreaterThan(0);
expect(resolveLeadDetailText('generateAiAction').length).toBeGreaterThan(0);
expect(resolveLeadDetailText('applyScriptAction').length).toBeGreaterThan(0);
expect(resolveLeadDetailText('noteSavedToast')).toBe('备忘已保存');
expect(resolveLeadDetailText('notesRequiredToast')).toBe('请输入备忘或上传附件');
});
});
describe('5. 销售触达与 AI 赋能函数', () => {
describe('5. 国际销售触达与 AI 赋能函数', () => {
it('5.1 formatWhatsAppUrl 清洗国际号码并生成 wa.me 链接', () => {
const { formatWhatsAppUrl } = loadUseLeadDetail();
@@ -367,6 +426,35 @@ describe('OmniCard AI 名片主潜客详情业务逻辑 (useLeadDetail.uts)', ()
expect(resWithAttach).toContain('Follow-up call agreed');
expect(resWithAttach).toContain('https://cdn.example.com/card.jpg');
});
it('5.4 parseNoteAttachments 提取历史备忘中的文本与图片附件链接', () => {
const { parseNoteAttachments } = loadUseLeadDetail();
const sample = 'Discussed custom pricing\n[https://cdn.example.com/card1.jpg https://cdn.example.com/card2.jpg]';
const parsed = parseNoteAttachments(sample);
expect(parsed.cleanText).toBe('Discussed custom pricing');
expect(parsed.imageUrls).toHaveLength(2);
expect(parsed.imageUrls[0]).toBe('https://cdn.example.com/card1.jpg');
expect(parsed.imageUrls[1]).toBe('https://cdn.example.com/card2.jpg');
const plain = parseNoteAttachments('Just plain text');
expect(plain.cleanText).toBe('Just plain text');
expect(plain.imageUrls).toHaveLength(0);
const empty = parseNoteAttachments('');
expect(empty.cleanText).toBe('');
expect(empty.imageUrls).toHaveLength(0);
});
it('7.3 createLeadDetailState 必须初始化 phone, email, source 字段', () => {
const { createLeadDetailState } = loadUseLeadDetail();
expect(typeof createLeadDetailState).toBe('function');
const state = createLeadDetailState!();
expect(state.phone).toBe('');
expect(state.email).toBe('');
expect(state.source).toBe('');
});
});
});
@@ -419,11 +507,12 @@ describe('OmniCard AI 名片主潜客详情页契约 (leads-detail.uvue)', () =>
expect(pageSource.includes('#F7F8FA')).toBe(true);
expect(pageSource.includes('12px')).toBe(true);
expect(pageSource.includes('16px')).toBe(true);
expect(
pageSource.includes('navigateBack') || logicSource.includes('navigateBack')
).toBe(true);
expect(pageSource.includes('notes-input')).toBe(true);
expect(pageSource.includes('font-size: 16px')).toBe(true);
expect(pageSource.includes('lead-cta-inner')).toBe(true);
expect(pageSource.includes('max-width: 480px')).toBe(true);
expect(pageSource.includes("'contactSaved'")).toBe(false);
});
it('无 emoji、无 any,模板无硬编码中文主文案', () => {
const pageSource = fs.readFileSync(DETAIL_PAGE, 'utf8');
const logicSource = fs.readFileSync(DETAIL_UTS, 'utf8');