155 lines
5.7 KiB
Vue
155 lines
5.7 KiB
Vue
<template>
|
|
<div class="page-card">
|
|
<h2 class="page-title">服务客户</h2>
|
|
<div class="summary-bar" v-if="summary">
|
|
客户数 {{ summary.customerCount }} · 待回访 {{ summary.pendingLeadCount }} · 本月到店 {{ summary.monthVisitCount }}
|
|
</div>
|
|
<div class="filter-row">
|
|
<el-select v-model="source" clearable placeholder="来源" style="width: 140px">
|
|
<el-option label="预约" value="appointment" />
|
|
<el-option label="报告留资" value="lead" />
|
|
</el-select>
|
|
<el-checkbox v-model="hasPendingLead">有待回访</el-checkbox>
|
|
<el-input v-model="q" clearable placeholder="姓名 / 手机 / 宠物名" style="width: 220px" />
|
|
<el-button type="primary" @click="load">查询</el-button>
|
|
</div>
|
|
<el-table :data="rows" v-loading="loading" row-key="storeCustomerId" stripe>
|
|
<el-table-column prop="displayName" label="客户" width="120" />
|
|
<el-table-column prop="phoneMasked" label="手机" width="140" />
|
|
<el-table-column label="宠物" min-width="160">
|
|
<template #default="{ row }">
|
|
<el-tag v-for="p in row.pets || []" :key="p.name" size="small" class="pet-tag">{{ p.name }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="lastVisitAt" label="最近到店" min-width="160" />
|
|
<el-table-column prop="leadStatus" label="留资状态" width="100" />
|
|
<el-table-column label="来源" width="140">
|
|
<template #default="{ row }">{{ formatSource(row.source) }}</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="200" fixed="right">
|
|
<template #default="{ row }">
|
|
<el-button link type="primary" @click="openDetail(row)">详情</el-button>
|
|
<el-button v-if="row.lastReportId" link @click="goReport(row)">报告</el-button>
|
|
<el-button v-if="row.hasPendingLead" link @click="goLead">回访</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<el-drawer v-model="drawer" title="客户详情" size="420px">
|
|
<template v-if="current">
|
|
<el-descriptions :column="1" border>
|
|
<el-descriptions-item label="姓名">{{ current.displayName }}</el-descriptions-item>
|
|
<el-descriptions-item label="手机">{{ current.phoneMasked }}</el-descriptions-item>
|
|
<el-descriptions-item label="首次来源">{{ formatOriginSource(current.originSource) }}</el-descriptions-item>
|
|
<el-descriptions-item label="首次接触">{{ current.firstContactAt || '—' }}</el-descriptions-item>
|
|
<el-descriptions-item label="最近接触">{{ current.lastContactAt || '—' }}</el-descriptions-item>
|
|
<el-descriptions-item label="最近到店">{{ current.lastVisitAt || '—' }}</el-descriptions-item>
|
|
<el-descriptions-item label="留资">{{ current.leadStatus || '—' }}</el-descriptions-item>
|
|
<el-descriptions-item label="宠物">
|
|
<el-tag v-for="p in current.pets || []" :key="p.name" size="small" class="pet-tag">{{ p.name }}</el-tag>
|
|
</el-descriptions-item>
|
|
</el-descriptions>
|
|
<div class="drawer-actions">
|
|
<el-button v-if="current.lastReportId" type="primary" @click="goReport(current)">最近报告</el-button>
|
|
<el-button v-if="current.hasPendingLead" @click="goLead">去回访</el-button>
|
|
</div>
|
|
</template>
|
|
</el-drawer>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { onMounted, ref } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { getServiceCustomers } from '@/api'
|
|
|
|
type CustomerRow = {
|
|
storeCustomerId: number
|
|
userId?: number | null
|
|
displayName?: string
|
|
phoneMasked?: string
|
|
pets?: Array<{ name: string }>
|
|
lastVisitAt?: string
|
|
lastReportId?: number | null
|
|
leadStatus?: string
|
|
hasPendingLead?: boolean
|
|
source?: string
|
|
originSource?: string
|
|
firstContactAt?: string
|
|
lastContactAt?: string
|
|
}
|
|
|
|
const router = useRouter()
|
|
const loading = ref(false)
|
|
const rows = ref<CustomerRow[]>([])
|
|
const summary = ref<{ customerCount: number; pendingLeadCount: number; monthVisitCount: number } | null>(null)
|
|
const source = ref('')
|
|
const hasPendingLead = ref(false)
|
|
const q = ref('')
|
|
const drawer = ref(false)
|
|
const current = ref<CustomerRow | null>(null)
|
|
|
|
async function load() {
|
|
loading.value = true
|
|
try {
|
|
const res = await getServiceCustomers({
|
|
source: source.value || undefined,
|
|
hasPendingLead: hasPendingLead.value || undefined,
|
|
q: q.value || undefined,
|
|
page: 1,
|
|
pageSize: 50,
|
|
})
|
|
const data = res.data as {
|
|
list?: CustomerRow[]
|
|
summary?: { customerCount: number; pendingLeadCount: number; monthVisitCount: number }
|
|
} | undefined
|
|
rows.value = data?.list || []
|
|
summary.value = data?.summary || null
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function openDetail(row: CustomerRow) {
|
|
current.value = row
|
|
drawer.value = true
|
|
}
|
|
|
|
function goReport(row: CustomerRow) {
|
|
if (!row.lastReportId) return
|
|
router.push({ path: '/reports', query: { q: String(row.lastReportId) } })
|
|
}
|
|
|
|
function goLead() {
|
|
router.push({ path: '/leads', query: { due: 'today' } })
|
|
}
|
|
|
|
function formatSource(value?: string) {
|
|
if (value === 'appointment+lead') return '预约 + 报告留资'
|
|
if (value === 'appointment') return '预约'
|
|
if (value === 'lead') return '报告留资'
|
|
return '—'
|
|
}
|
|
|
|
function formatOriginSource(value?: string) {
|
|
if (value === 'customer_booking') return '宠主自助预约'
|
|
if (value === 'assisted_booking') return '门店代客预约'
|
|
if (value === 'report_lead') return '报告留资'
|
|
if (value === 'appointment') return '历史预约'
|
|
return '—'
|
|
}
|
|
|
|
onMounted(load)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.pet-tag {
|
|
margin-right: 4px;
|
|
}
|
|
.drawer-actions {
|
|
margin-top: 16px;
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
</style>
|