新增
This commit is contained in:
@@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<div class="qywx-customer-page p-4">
|
||||
<el-card shadow="never" class="mb-4">
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">企业微信客户管理</span>
|
||||
<div class="flex gap-2">
|
||||
<el-button type="primary" :loading="syncing" @click="syncCustomers">
|
||||
<template #icon><Refresh /></template>
|
||||
立即同步
|
||||
</el-button>
|
||||
<el-button @click="showSyncSettings = true">
|
||||
<template #icon><Setting /></template>
|
||||
同步设置
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-alert
|
||||
title="功能说明"
|
||||
type="info"
|
||||
:closable="false"
|
||||
class="mb-4"
|
||||
>
|
||||
<div class="text-sm">
|
||||
<p>自动从企业微信同步外部联系人(客户)信息,包括客户基本信息、跟进人等。</p>
|
||||
<p class="mt-1">同步后的数据可用于患者信息匹配和自动关联。</p>
|
||||
</div>
|
||||
</el-alert>
|
||||
|
||||
<div class="grid grid-cols-4 gap-4 mb-4">
|
||||
<el-card shadow="hover">
|
||||
<div class="text-gray-500 text-sm mb-1">总客户数</div>
|
||||
<div class="text-2xl font-bold text-primary">{{ stats.total }}</div>
|
||||
</el-card>
|
||||
<el-card shadow="hover">
|
||||
<div class="text-gray-500 text-sm mb-1">今日新增</div>
|
||||
<div class="text-2xl font-bold text-success">{{ stats.today }}</div>
|
||||
</el-card>
|
||||
<el-card shadow="hover">
|
||||
<div class="text-gray-500 text-sm mb-1">最后同步</div>
|
||||
<div class="text-sm font-medium">{{ stats.lastSync || '未同步' }}</div>
|
||||
</el-card>
|
||||
<el-card shadow="hover">
|
||||
<div class="text-gray-500 text-sm mb-1">同步状态</div>
|
||||
<el-tag :type="stats.syncStatus === 'success' ? 'success' : 'info'" size="small">
|
||||
{{ stats.syncStatusText }}
|
||||
</el-tag>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never">
|
||||
<el-form :model="queryParams" inline class="mb-4">
|
||||
<el-form-item label="客户名称">
|
||||
<el-input
|
||||
v-model="queryParams.name"
|
||||
placeholder="搜索客户名称"
|
||||
clearable
|
||||
@keyup.enter="resetPage"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="跟进人">
|
||||
<el-input
|
||||
v-model="queryParams.follow_user"
|
||||
placeholder="跟进人姓名"
|
||||
clearable
|
||||
@keyup.enter="resetPage"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="resetPage">查询</el-button>
|
||||
<el-button @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="pager.loading" :data="pager.lists" stripe>
|
||||
<el-table-column label="客户信息" min-width="200">
|
||||
<template #default="{ row }">
|
||||
<div class="flex items-center gap-2">
|
||||
<el-avatar :size="40" :src="row.avatar" />
|
||||
<div>
|
||||
<div class="font-medium">{{ row.name }}</div>
|
||||
<div class="text-xs text-gray-400">{{ row.external_userid }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small">{{ row.type === 1 ? '微信' : '企微' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="性别" width="60">
|
||||
<template #default="{ row }">
|
||||
{{ row.gender === 1 ? '男' : row.gender === 2 ? '女' : '未知' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="跟进人" min-width="150">
|
||||
<template #default="{ row }">
|
||||
<div v-if="row.follow_users && row.follow_users.length">
|
||||
<el-tag
|
||||
v-for="(user, idx) in row.follow_users"
|
||||
:key="idx"
|
||||
size="small"
|
||||
class="mr-1 mb-1"
|
||||
>
|
||||
{{ user.name }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<span v-else class="text-gray-400">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="添加时间" width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatTime(row.create_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="更新时间" width="160">
|
||||
<template #default="{ row }">
|
||||
{{ formatTime(row.update_time) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button type="primary" link @click="viewDetail(row)">查看详情</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="flex justify-end mt-4">
|
||||
<pagination v-model="pager" @change="getLists" />
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 同步设置对话框 -->
|
||||
<el-dialog
|
||||
v-model="showSyncSettings"
|
||||
title="同步设置"
|
||||
width="500px"
|
||||
>
|
||||
<el-form :model="syncSettings" label-width="120px">
|
||||
<el-form-item label="自动同步">
|
||||
<el-switch v-model="syncSettings.auto_sync" />
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
开启后将定时自动同步企业微信客户数据
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="同步间隔">
|
||||
<el-select v-model="syncSettings.interval" :disabled="!syncSettings.auto_sync">
|
||||
<el-option label="每小时" :value="3600" />
|
||||
<el-option label="每2小时" :value="7200" />
|
||||
<el-option label="每4小时" :value="14400" />
|
||||
<el-option label="每6小时" :value="21600" />
|
||||
<el-option label="每12小时" :value="43200" />
|
||||
<el-option label="每天" :value="86400" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showSyncSettings = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveSyncSettings">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 客户详情对话框 -->
|
||||
<el-dialog
|
||||
v-model="showDetail"
|
||||
title="客户详情"
|
||||
width="600px"
|
||||
>
|
||||
<el-descriptions v-if="currentCustomer" :column="2" border>
|
||||
<el-descriptions-item label="客户名称">{{ currentCustomer.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="External ID">{{ currentCustomer.external_userid }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">
|
||||
{{ currentCustomer.gender === 1 ? '男' : currentCustomer.gender === 2 ? '女' : '未知' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="类型">
|
||||
{{ currentCustomer.type === 1 ? '微信用户' : '企业微信用户' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="企业名称" :span="2">
|
||||
{{ currentCustomer.corp_name || '—' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="职位" :span="2">
|
||||
{{ currentCustomer.position || '—' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="添加时间" :span="2">
|
||||
{{ formatTime(currentCustomer.create_time) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间" :span="2">
|
||||
{{ formatTime(currentCustomer.update_time) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onUnmounted } from 'vue'
|
||||
import { Refresh, Setting } from '@element-plus/icons-vue'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import feedback from '@/utils/feedback'
|
||||
import {
|
||||
qywxCustomerLists,
|
||||
qywxCustomerSync,
|
||||
qywxCustomerStats,
|
||||
qywxSyncSettingsGet,
|
||||
qywxSyncSettingsSave
|
||||
} from '@/api/qywx'
|
||||
|
||||
const syncing = ref(false)
|
||||
const showSyncSettings = ref(false)
|
||||
const showDetail = ref(false)
|
||||
const currentCustomer = ref<any>(null)
|
||||
|
||||
const stats = reactive({
|
||||
total: 0,
|
||||
today: 0,
|
||||
lastSync: '',
|
||||
syncStatus: 'idle',
|
||||
syncStatusText: '未同步'
|
||||
})
|
||||
|
||||
const syncSettings = reactive({
|
||||
auto_sync: false,
|
||||
interval: 3600
|
||||
})
|
||||
|
||||
const queryParams = reactive({
|
||||
name: '',
|
||||
follow_user: ''
|
||||
})
|
||||
|
||||
let syncTimer: number | null = null
|
||||
|
||||
async function fetchLists(params: Record<string, unknown>) {
|
||||
return qywxCustomerLists(params)
|
||||
}
|
||||
|
||||
const { pager, getLists, resetPage, resetParams } = usePaging({
|
||||
fetchFun: fetchLists,
|
||||
params: queryParams
|
||||
})
|
||||
|
||||
function handleReset() {
|
||||
queryParams.name = ''
|
||||
queryParams.follow_user = ''
|
||||
resetParams()
|
||||
}
|
||||
|
||||
async function syncCustomers() {
|
||||
syncing.value = true
|
||||
try {
|
||||
await qywxCustomerSync()
|
||||
feedback.msgSuccess('同步成功')
|
||||
await loadStats()
|
||||
await getLists()
|
||||
} catch (e: any) {
|
||||
feedback.msgError(e?.message || '同步失败')
|
||||
} finally {
|
||||
syncing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
const res: any = await qywxCustomerStats()
|
||||
Object.assign(stats, res.data || res)
|
||||
} catch (e) {
|
||||
console.error('加载统计失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSyncSettings() {
|
||||
try {
|
||||
const res: any = await qywxSyncSettingsGet()
|
||||
Object.assign(syncSettings, res.data || res)
|
||||
|
||||
// 如果开启了自动同步,启动定时器
|
||||
if (syncSettings.auto_sync) {
|
||||
startSyncTimer()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载同步设置失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSyncSettings() {
|
||||
try {
|
||||
await qywxSyncSettingsSave(syncSettings)
|
||||
feedback.msgSuccess('保存成功')
|
||||
showSyncSettings.value = false
|
||||
|
||||
// 重新启动定时器
|
||||
stopSyncTimer()
|
||||
if (syncSettings.auto_sync) {
|
||||
startSyncTimer()
|
||||
}
|
||||
} catch (e: any) {
|
||||
feedback.msgError(e?.message || '保存失败')
|
||||
}
|
||||
}
|
||||
|
||||
function startSyncTimer() {
|
||||
if (syncTimer) return
|
||||
|
||||
syncTimer = window.setInterval(() => {
|
||||
syncCustomers()
|
||||
}, syncSettings.interval * 1000)
|
||||
}
|
||||
|
||||
function stopSyncTimer() {
|
||||
if (syncTimer) {
|
||||
clearInterval(syncTimer)
|
||||
syncTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
function viewDetail(row: any) {
|
||||
currentCustomer.value = row
|
||||
showDetail.value = true
|
||||
}
|
||||
|
||||
function formatTime(timestamp: number | string) {
|
||||
if (!timestamp) return '—'
|
||||
const date = new Date(typeof timestamp === 'number' ? timestamp * 1000 : timestamp)
|
||||
return date.toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getLists()
|
||||
loadStats()
|
||||
loadSyncSettings()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
stopSyncTimer()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.qywx-customer-page {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user