feat(customer): 新增ICC客户信息同步功能 - OAuth2密码模式鉴权拉取org_base客户、按icc_code幂等upsert写入并记录icc_sync_log,注册每10分钟定时任务;客户档案管理页新增手动'同步客户'按钮(POST /api/customers/sync/icc),同步后自动刷新列表与统计;customer表新增icc_code字段及唯一索引,同步db_seed与设计文档

This commit is contained in:
wk authored and trae-solo-agent committed 2026-10-23 18:20:25 +08:00
1 parent be91d04431
commit 9d3bb13ecf
24 files changed
+1159 -5

No files matched your search

+2
View File
@@ -86,6 +86,8 @@ export const customerApi = {
getCustomerStats: () => request.get<CustomerStats>('/customers/stats'),
syncIccCustomers: () => request.post('/customers/sync/icc'),
createCustomer: (data: CustomerCreateParams) =>
request.post('/customers', data),
+39 -1
View File
@@ -19,6 +19,10 @@
<p class="desc">统一维护客户的基础资料、联系信息与业务阶段,支持快速检索与档案管理。</p>
</div>
<div class="header-actions">
<a-button :loading="syncing" @click="handleSync">
<template #icon><SyncOutlined /></template>
同步客户
</a-button>
<a-button type="primary" @click="handleAdd">
<template #icon><PlusOutlined /></template>
新增客户
@@ -323,13 +327,14 @@
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue';
import { message } from 'ant-design-vue';
import { message, Modal } from 'ant-design-vue';
import {
SearchOutlined,
ReloadOutlined,
PlusOutlined,
DeleteOutlined,
EditOutlined,
SyncOutlined,
TeamOutlined,
AimOutlined,
FileProtectOutlined,
@@ -352,6 +357,7 @@ const permissionStore = usePermissionStore();
// ==================== 数据 ====================
const loading = ref(false);
const syncing = ref(false);
const dataSource = ref<CustomerInfo[]>([]);
const total = ref(0);
const editNo = ref<string>('');
@@ -568,6 +574,38 @@ function getSalesmanName(id?: number) {
return found?.label ?? '-';
}
// ==================== ICC 手动同步 ====================
function handleSync() {
Modal.confirm({
title: '同步客户',
content: '将从 ICC 平台拉取客户信息,并按 ICC 客户编号幂等新增/更新本地客户档案,是否继续?',
okText: '开始同步',
cancelText: '取消',
onOk: async () => {
syncing.value = true;
try {
const res: any = await customerApi.syncIccCustomers();
if (res.code === 200) {
const d = res.data || {};
const pulled = d.pulled ?? 0;
const inserted = d.inserted ?? 0;
const updated = d.updated ?? 0;
const skipped = d.skipped ?? 0;
message.success(`同步完成:拉取 ${pulled} 条,新增 ${inserted} 条,更新 ${updated} 条,跳过 ${skipped} 条`);
fetchList();
fetchStats();
} else {
message.error(res.message || '同步失败');
}
} catch (error) {
message.error('同步失败,请稍后重试');
} finally {
syncing.value = false;
}
},
});
}
// ==================== 搜索重置 ====================
function handleSearch() {
searchForm.page = 1;
+1
View File
@@ -66,6 +66,7 @@ export default defineConfig({
}
},
server: {
host: "0.0.0.0",
port: 3000,
proxy: {
"/api": {