feat: 移动端商机跟单新增跟进记录时间线+添加功能
编辑模式下表单底部显示跟进时间线 + 新增跟进入口 与PC端一致的体验:方式chip选择 + 日期 + 内容 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,7 @@ onMounted(async () => {
|
||||
}
|
||||
form.value = { customer_id: d.customer_id, product_type: d.product_type, amount: d.amount, follow_up_detail: d.follow_up_detail || '', status: d.status, expected_revenue_date: d.expected_revenue_date, manager_id: d.manager_id }
|
||||
} catch (_) {}
|
||||
loadLogs()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -100,6 +101,45 @@ async function handleDelete() {
|
||||
router.push('/m/mini-biz')
|
||||
} catch (e: any) { if (e !== 'cancel') ElMessage.error('删除失败') }
|
||||
}
|
||||
|
||||
// ── Follow-up logs ──
|
||||
const logs = ref<any[]>([])
|
||||
const logForm = ref({ log_date: '', method: '电话', content: '' })
|
||||
const logMethods = ['电话', '微信', '上门', '邮件', '其他']
|
||||
const logMethodIcons: Record<string, string> = { '电话': '📞', '微信': '💬', '上门': '🏢', '邮件': '📧', '其他': '📋' }
|
||||
const logMethodColors: Record<string, string> = { '电话': '#5B7FA5', '微信': '#22c55e', '上门': '#4A6741', '邮件': '#C4934A', '其他': '#7B7568' }
|
||||
const logLoading = ref(false)
|
||||
const logSaving = ref(false)
|
||||
|
||||
async function loadLogs() {
|
||||
logLoading.value = true
|
||||
try {
|
||||
const res = await miniBusinessApi.getLogs(route.params.id as string)
|
||||
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(route.params.id as string, 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('删除失败') }
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -175,6 +215,37 @@ async function handleDelete() {
|
||||
{{ isEdit ? '保存修改' : '提交商机' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Follow-up Timeline (edit mode only) -->
|
||||
<div v-if="isEdit" class="log-section">
|
||||
<div class="section-head"><span class="section-title">跟进记录</span><span class="section-line"></span></div>
|
||||
<div v-loading="logLoading" class="log-list">
|
||||
<div v-if="logs.length === 0 && !logLoading" class="log-empty">暂无跟进记录</div>
|
||||
<div v-for="l in logs" :key="l.id" class="log-item">
|
||||
<span class="log-icon">{{ logMethodIcons[l.method] || '📋' }}</span>
|
||||
<div class="log-body">
|
||||
<div class="log-header">
|
||||
<span class="log-date">{{ l.log_date }}</span>
|
||||
<span class="log-chip" :style="{ background: logMethodColors[l.method] || '#7B7568', color: '#fff', padding: '1px 6px', fontSize: '11px' }">{{ l.method }}</span>
|
||||
<span class="log-author">{{ l.created_by_name }}</span>
|
||||
</div>
|
||||
<div class="log-content">{{ l.content }}</div>
|
||||
</div>
|
||||
<button class="log-del" @click="handleDeleteLog(l.id)">✕</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add Log -->
|
||||
<div class="log-add">
|
||||
<div class="log-add-row">
|
||||
<el-date-picker v-model="logForm.log_date" type="date" value-format="YYYY-MM-DD" size="small" style="width:130px" />
|
||||
<div class="log-chips">
|
||||
<button v-for="m in logMethods" :key="m" type="button" class="log-chip-btn" :class="{ 'log-chip-btn--active': logForm.method === m }" :style="logForm.method === m ? { background: logMethodColors[m], borderColor: logMethodColors[m], color: '#fff' } : {}" @click="logForm.method = m">{{ m }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<el-input v-model="logForm.content" type="textarea" :rows="2" placeholder="输入跟进内容..." size="small" style="margin-top:6px" />
|
||||
<button class="log-submit" :disabled="logSaving" @click="handleCreateLog">{{ logSaving ? '添加中...' : '添加跟进' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
@@ -258,4 +329,31 @@ async function handleDelete() {
|
||||
.btn-loading { width: 16px; height: 16px; border: 2px solid rgba(255,255,255,0.3); border-top-color: #fff; border-radius: 50%; animation: spin 0.6s linear infinite; }
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
.required-star { color: var(--vermilion); font-weight: 700; }
|
||||
|
||||
/* ── Follow-up Log Section ── */
|
||||
.log-section { margin-top: 20px; border-top: 1px solid var(--warm-border); padding-top: 16px; }
|
||||
.section-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
|
||||
.section-title { font-family: var(--font-heading); font-size: 18px; color: var(--ink); letter-spacing: 0.06em; white-space: nowrap; }
|
||||
.section-line { flex: 1; height: 2px; background: var(--gold); }
|
||||
.log-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; max-height: 260px; overflow-y: auto; }
|
||||
.log-empty { text-align: center; color: var(--warm-gray); font-size: 13px; padding: 16px 0; }
|
||||
.log-item { display: flex; gap: 8px; align-items: flex-start; padding: 10px; background: var(--surface); border: 1px solid var(--warm-border); }
|
||||
.log-icon { font-size: 16px; flex-shrink: 0; margin-top: 2px; }
|
||||
.log-body { flex: 1; min-width: 0; }
|
||||
.log-header { display: flex; align-items: center; gap: 6px; margin-bottom: 3px; }
|
||||
.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.5; }
|
||||
.log-del { flex-shrink: 0; background: none; border: none; color: var(--warm-gray); cursor: pointer; font-size: 14px; padding: 2px; }
|
||||
.log-del:hover { color: var(--vermilion); }
|
||||
.log-chip { border-radius: 2px; white-space: nowrap; }
|
||||
.log-add { border-top: 1px solid var(--warm-border); padding-top: 12px; }
|
||||
.log-add-row { display: flex; gap: 6px; align-items: center; }
|
||||
.log-chips { display: flex; gap: 3px; }
|
||||
.log-chip-btn { padding: 4px 8px; border: 1px solid var(--warm-border); background: var(--surface); font-size: 11px; cursor: pointer; transition: all 0.2s; }
|
||||
.log-chip-btn:hover { border-color: var(--ink); }
|
||||
.log-chip-btn--active { font-weight: 600; }
|
||||
.log-submit { width: 100%; margin-top: 8px; padding: 10px; background: var(--ink); color: #fff; border: none; font-family: var(--font-heading); font-size: 14px; letter-spacing: 0.06em; cursor: pointer; }
|
||||
.log-submit:hover { background: var(--ink-light); }
|
||||
.log-submit:disabled { opacity: 0.6; cursor: not-allowed; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user