From 19aa9153ff7ef6e6b908536109ad4c62ae8f0733 Mon Sep 17 00:00:00 2001 From: v6ole Date: Sun, 12 Jul 2026 18:53:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20PC=E7=AB=AF=E5=95=86=E6=9C=BA=E8=B7=9F?= =?UTF-8?q?=E5=8D=95=E6=96=B0=E5=A2=9E=E8=AF=A6=E6=83=85=E5=BC=B9=E7=AA=97?= =?UTF-8?q?+=E8=B7=9F=E8=BF=9B=E6=97=B6=E9=97=B4=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 点击客户名打开详情弹窗(替代直接编辑) - 详情弹窗: 商机信息 + 跟进时间线 + 添加入口 - 跟进方式 chip: 电话/微信/上门/邮件/其他(色标区分) - 每条跟进记录显示: 图标+日期+方式+内容+作者 - 支持添加和删除跟进记录 Co-Authored-By: Claude --- frontend/src/views/desktop/MiniBusiness.vue | 133 +++++++++++++++++++- 1 file changed, 132 insertions(+), 1 deletion(-) diff --git a/frontend/src/views/desktop/MiniBusiness.vue b/frontend/src/views/desktop/MiniBusiness.vue index fbd7364..e52923a 100644 --- a/frontend/src/views/desktop/MiniBusiness.vue +++ b/frontend/src/views/desktop/MiniBusiness.vue @@ -25,6 +25,17 @@ const form = ref({}) const statusPick = ref>({}) const miniStatuses = ['跟进中', '已签约', '已流失'] +// ── Detail + Follow-up logs ── +const detailVisible = ref(false) +const selectedBusiness = ref(null) +const logs = ref([]) +const logForm = ref({ log_date: '', method: '电话', content: '' }) +const logMethods = ['电话', '微信', '上门', '邮件', '其他'] +const logMethodIcons: Record = { '电话': '📞', '微信': '💬', '上门': '🏢', '邮件': '📧', '其他': '📋' } +const logMethodColors: Record = { '电话': '#5B7FA5', '微信': '#22c55e', '上门': '#4A6741', '邮件': '#C4934A', '其他': '#7B7568' } +const logLoading = ref(false) +const logSaving = ref(false) + // ── Filters ── const filterManager = ref('') const filterStatus = ref('') @@ -149,6 +160,44 @@ async function handleDelete(id: string) { } catch (e: any) { if (e !== 'cancel') ElMessage.error('删除失败') } } +// ── Detail + Follow-up logs ── +async function openDetail(item: any) { + selectedBusiness.value = item + detailVisible.value = true + logForm.value = { log_date: new Date().toISOString().slice(0, 10), method: '电话', content: '' } + await loadLogs() +} + +async function loadLogs() { + logLoading.value = true + try { + const res = await miniBusinessApi.getLogs(selectedBusiness.value.id) + logs.value = res.data || [] + } catch (_) { logs.value = [] } + finally { logLoading.value = false } +} + +async function handleCreateLog() { + if (!logForm.value.content.trim()) { ElMessage.warning('请输入跟进内容'); return } + logSaving.value = true + try { + await miniBusinessApi.createLog(selectedBusiness.value.id, logForm.value) + ElMessage.success('跟进记录已添加') + logForm.value = { log_date: new Date().toISOString().slice(0, 10), method: '电话', content: '' } + await loadLogs() + } catch (e: any) { ElMessage.error('添加失败: ' + (e.response?.data?.detail || e.message)) } + finally { logSaving.value = false } +} + +async function handleDeleteLog(logId: string) { + try { + await ElMessageBox.confirm('确定删除该跟进记录?', '确认', { type: 'warning' }) + await miniBusinessApi.deleteLog(logId) + ElMessage.success('已删除') + await loadLogs() + } catch (e: any) { if (e !== 'cancel') ElMessage.error('删除失败') } +} + async function quickStatusChange(row: any, newStatus: string) { try { await ElMessageBox.confirm(`确定将状态改为「${newStatus}」?`, '确认', { type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }) @@ -220,7 +269,7 @@ async function quickStatusChange(row: any, newStatus: string) { + + + + +
+
产品类型{{ selectedBusiness.product_type }}
+
金额{{ selectedBusiness.amount }}
+
状态{{ selectedBusiness.status }}
+
预计列收{{ selectedBusiness.expected_revenue_date }}
+
客户经理{{ selectedBusiness.manager_name }}
+
商机概述{{ selectedBusiness.follow_up_detail }}
+
+ +
+ 编辑商机 +
+ + +
+

跟进记录

+
+
暂无跟进记录
+
+ {{ logMethodIcons[l.method] || '📋' }} +
+
+ {{ l.log_date }} + {{ l.method }} + {{ l.created_by_name }} +
+
{{ l.content }}
+
+ 删除 +
+
+ + +
+
+ +
+ +
+
+ + 添加跟进 +
+
+ + +
@@ -326,4 +428,33 @@ async function quickStatusChange(row: any, newStatus: string) { .quick-create-btn { display: inline-flex; align-items: center; gap: 4px; background: none; border: 1px dashed var(--gold); padding: 6px 12px; color: var(--gold-dark); font-family: var(--font-body); font-size: 12px; cursor: pointer; transition: all 0.2s; } .quick-create-btn:hover { border-color: var(--vermilion); color: var(--vermilion); background: rgba(184,71,46,0.03); } .select-empty-create { padding: 8px 12px; text-align: center; } + +/* ── Detail Dialog ── */ +.detail-info { background: var(--c-bg-light, #faf9f6); border-radius: 6px; padding: 14px 18px; } +.detail-row { display: flex; gap: 12px; padding: 4px 0; font-size: 13px; } +.detail-label { color: var(--warm-gray); min-width: 70px; flex-shrink: 0; } + +/* ── Log Timeline ── */ +.log-section { margin-top: 18px; border-top: 1px solid var(--warm-border); padding-top: 14px; } +.log-title { margin: 0 0 12px; font-family: var(--font-heading); font-size: 16px; color: var(--ink); } +.log-list { display: flex; flex-direction: column; gap: 10px; max-height: 300px; overflow-y: auto; margin-bottom: 14px; } +.log-empty { text-align: center; color: var(--warm-gray); font-size: 13px; padding: 20px 0; } +.log-item { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; background: var(--surface); border: 1px solid var(--warm-border); } +.log-icon { font-size: 18px; flex-shrink: 0; margin-top: 2px; } +.log-body { flex: 1; min-width: 0; } +.log-header { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; } +.log-date { font-family: var(--font-mono); font-size: 12px; color: var(--ink); } +.log-author { font-size: 11px; color: var(--warm-gray); margin-left: auto; } +.log-content { font-size: 13px; color: var(--c-text); line-height: 1.6; } +.log-del { flex-shrink: 0; opacity: 0.5; } +.log-del:hover { opacity: 1; } +.log-method-chip { border-radius: 3px; white-space: nowrap; } + +/* ── Add Log Form ── */ +.log-add { border-top: 1px solid var(--warm-border); padding-top: 12px; } +.log-add-row { display: flex; gap: 8px; align-items: center; } +.log-method-chips { display: flex; gap: 4px; } +.log-chip { padding: 4px 10px; border: 1px solid var(--warm-border); background: var(--surface); font-size: 12px; cursor: pointer; transition: all 0.2s; } +.log-chip:hover { border-color: var(--ink); } +.log-chip--active { font-weight: 600; }