fix(client): 修复极速名片编辑与潜客详情白屏,补齐战报空状态与AI回退逻辑
这个提交包含在:
@@ -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');
|
||||
|
||||
在新工单中引用
屏蔽一个用户