Skip to content

Commit 0ae6139

Browse files
committed
📖 CRM:线索的转化逻辑
1 parent 1047c5b commit 0ae6139

File tree

10 files changed

+309
-11
lines changed

10 files changed

+309
-11
lines changed

src/api/crm/clue/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,16 @@ export interface ClueVO {
2020
wechat: string // wechat
2121
email: string // email
2222
areaId: number // 所在地
23+
areaName?: string // 所在地名称
2324
detailAddress: string // 详细地址
2425
industryId: number // 所属行业
2526
level: number // 客户等级
2627
source: number // 客户来源
2728
remark: string // 备注
29+
creator: string // 创建人
30+
creatorName?: string // 创建人名称
31+
createTime: Date // 创建时间
32+
updateTime: Date // 更新时间
2833
}
2934

3035
// 查询线索列表
@@ -61,3 +66,8 @@ export const exportClue = async (params) => {
6166
export const transferClue = async (data: TransferReqVO) => {
6267
return await request.put({ url: '/crm/clue/transfer', data })
6368
}
69+
70+
// 线索转化为客户
71+
export const transformClue = async (ids: number[]) => {
72+
return await request.put({ url: '/crm/clue/transform?ids=' + ids.join(',') })
73+
}

src/api/crm/permission/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface PermissionVO {
1919
* @author HUIHUI
2020
*/
2121
export enum BizTypeEnum {
22-
CRM_LEADS = 1, // 线索
22+
CRM_CLUE = 1, // 线索
2323
CRM_CUSTOMER = 2, // 客户
2424
CRM_CONTACT = 3, // 联系人
2525
CRM_BUSINESS = 4, // 商机

src/router/modules/remaining.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,17 @@ const remainingRouter: AppRouteRecordRaw[] = [
496496
name: 'CrmCenter',
497497
meta: { hidden: true },
498498
children: [
499+
{
500+
path: 'clue/detail/:id',
501+
name: 'CrmClueDetail',
502+
meta: {
503+
title: '线索详情',
504+
noCache: true,
505+
hidden: true,
506+
activeMenu: '/crm/clue'
507+
},
508+
component: () => import('@/views/crm/clue/detail/index.vue')
509+
},
499510
{
500511
path: 'customer/detail/:id',
501512
name: 'CrmCustomerDetail',

src/views/crm/clue/ClueForm.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,12 @@
128128
/>
129129
</el-form-item>
130130
</el-col>
131+
<el-col :span="12">
132+
<el-form-item label="备注" prop="remark">
133+
<el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" />
134+
</el-form-item>
135+
</el-col>
131136
</el-row>
132-
<el-col :span="24">
133-
<el-form-item label="备注" prop="remark">
134-
<el-input v-model="formData.remark" placeholder="请输入备注" />
135-
</el-form-item>
136-
</el-col>
137137
</el-form>
138138
<template #footer>
139139
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<template>
2+
<div v-loading="loading">
3+
<div class="flex items-start justify-between">
4+
<div>
5+
<!-- 左上:线索基本信息 -->
6+
<el-col>
7+
<el-row>
8+
<span class="text-xl font-bold">{{ clue.name }}</span>
9+
</el-row>
10+
</el-col>
11+
</div>
12+
<div>
13+
<!-- 右上:按钮 -->
14+
<slot></slot>
15+
</div>
16+
</div>
17+
</div>
18+
<ContentWrap class="mt-10px">
19+
<el-descriptions :column="5" direction="vertical">
20+
<el-descriptions-item label="线索来源">
21+
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="clue.source" />
22+
</el-descriptions-item>
23+
<el-descriptions-item label="手机"> {{ clue.mobile }} </el-descriptions-item>
24+
<el-descriptions-item label="负责人">
25+
{{ clue.ownerUserName }}
26+
</el-descriptions-item>
27+
<el-descriptions-item label="创建时间">
28+
{{ formatDate(clue.createTime) }}
29+
</el-descriptions-item>
30+
</el-descriptions>
31+
</ContentWrap>
32+
</template>
33+
<script lang="ts" setup>
34+
import { DICT_TYPE } from '@/utils/dict'
35+
import * as ClueApi from '@/api/crm/clue'
36+
import { formatDate } from '@/utils/formatTime'
37+
38+
defineOptions({ name: 'ClueDetailsHeader' })
39+
defineProps<{
40+
clue: ClueApi.ClueVO // 线索信息
41+
loading: boolean // 加载中
42+
}>()
43+
</script>
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<template>
2+
<ContentWrap>
3+
<el-collapse v-model="activeNames" class="">
4+
<el-collapse-item name="basicInfo">
5+
<template #title>
6+
<span class="text-base font-bold">基本信息</span>
7+
</template>
8+
<el-descriptions :column="4">
9+
<el-descriptions-item label="线索名称">
10+
{{ clue.name }}
11+
</el-descriptions-item>
12+
<el-descriptions-item label="客户来源">
13+
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="clue.source" />
14+
</el-descriptions-item>
15+
<el-descriptions-item label="手机">{{ clue.mobile }}</el-descriptions-item>
16+
<el-descriptions-item label="电话">{{ clue.telephone }}</el-descriptions-item>
17+
<el-descriptions-item label="邮箱">{{ clue.email }}</el-descriptions-item>
18+
<el-descriptions-item label="地址">
19+
{{ clue.areaName }} {{ clue.detailAddress }}
20+
</el-descriptions-item>
21+
<el-descriptions-item label="QQ">{{ clue.qq }}</el-descriptions-item>
22+
<el-descriptions-item label="微信">{{ clue.wechat }}</el-descriptions-item>
23+
<el-descriptions-item label="客户行业">
24+
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="clue.industryId" />
25+
</el-descriptions-item>
26+
<el-descriptions-item label="客户级别">
27+
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="clue.level" />
28+
</el-descriptions-item>
29+
<el-descriptions-item label="下次联系时间">
30+
{{ formatDate(clue.contactNextTime) }}
31+
</el-descriptions-item>
32+
<el-descriptions-item label="备注">{{ clue.remark }}</el-descriptions-item>
33+
</el-descriptions>
34+
</el-collapse-item>
35+
<el-collapse-item name="systemInfo">
36+
<template #title>
37+
<span class="text-base font-bold">系统信息</span>
38+
</template>
39+
<el-descriptions :column="4">
40+
<el-descriptions-item label="负责人">{{ clue.ownerUserName }}</el-descriptions-item>
41+
<el-descriptions-item label="最后跟进记录">
42+
{{ clue.contactLastContent }}
43+
</el-descriptions-item>
44+
<el-descriptions-item label="最后跟进时间">
45+
{{ formatDate(clue.contactLastTime) }}
46+
</el-descriptions-item>
47+
<el-descriptions-item label="">&nbsp;</el-descriptions-item>
48+
<el-descriptions-item label="创建人">{{ clue.creatorName }}</el-descriptions-item>
49+
<el-descriptions-item label="创建时间">
50+
{{ formatDate(clue.createTime) }}
51+
</el-descriptions-item>
52+
<el-descriptions-item label="更新时间">
53+
{{ formatDate(clue.updateTime) }}
54+
</el-descriptions-item>
55+
</el-descriptions>
56+
</el-collapse-item>
57+
</el-collapse>
58+
</ContentWrap>
59+
</template>
60+
<script lang="ts" setup>
61+
import * as ClueApi from '@/api/crm/clue'
62+
import { DICT_TYPE } from '@/utils/dict'
63+
import { formatDate } from '@/utils/formatTime'
64+
65+
defineOptions({ name: 'ClueDetailsInfo' })
66+
const { clue } = defineProps<{
67+
clue: ClueApi.ClueVO // 线索明细
68+
}>()
69+
70+
const activeNames = ref(['basicInfo', 'systemInfo']) // 展示的折叠面板
71+
</script>
72+
<style lang="scss" scoped></style>

src/views/crm/clue/detail/index.vue

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<template>
2+
<ClueDetailsHeader :clue="clue" :loading="loading">
3+
<el-button
4+
v-if="permissionListRef?.validateWrite"
5+
v-hasPermi="['crm:clue:update']"
6+
type="primary"
7+
@click="openForm"
8+
>
9+
编辑
10+
</el-button>
11+
<el-button v-if="permissionListRef?.validateOwnerUser" type="primary" @click="transfer">
12+
转移
13+
</el-button>
14+
<el-button
15+
v-if="permissionListRef?.validateOwnerUser && !clue.transformStatus"
16+
type="success"
17+
@click="handleTransform"
18+
>
19+
转化为客户
20+
</el-button>
21+
<el-button type="success" disabled>已转化客户</el-button>
22+
</ClueDetailsHeader>
23+
<el-col>
24+
<el-tabs>
25+
<el-tab-pane label="跟进记录">
26+
<FollowUpList :biz-id="clueId" :biz-type="BizTypeEnum.CRM_CLUE" />
27+
</el-tab-pane>
28+
<el-tab-pane label="基本信息">
29+
<ClueDetailsInfo :clue="clue" />
30+
</el-tab-pane>
31+
<el-tab-pane label="团队成员">
32+
<PermissionList
33+
ref="permissionListRef"
34+
:biz-id="clue.id!"
35+
:biz-type="BizTypeEnum.CRM_CLUE"
36+
:show-action="!permissionListRef?.isPool || false"
37+
@quit-team="close"
38+
/>
39+
</el-tab-pane>
40+
<el-tab-pane label="操作日志">
41+
<OperateLogV2 :log-list="logList" />
42+
</el-tab-pane>
43+
</el-tabs>
44+
</el-col>
45+
46+
<!-- 表单弹窗:添加/修改 -->
47+
<ClueForm ref="formRef" @success="getClue" />
48+
<CrmTransferForm ref="transferFormRef" @success="close" />
49+
</template>
50+
<script lang="ts" setup>
51+
import { useTagsViewStore } from '@/store/modules/tagsView'
52+
import * as ClueApi from '@/api/crm/clue'
53+
import ClueForm from '@/views/crm/clue/ClueForm.vue'
54+
import ClueDetailsHeader from './ClueDetailsHeader.vue' // 线索明细 - 头部
55+
import ClueDetailsInfo from './ClueDetailsInfo.vue' // 线索明细 - 详细信息
56+
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限)
57+
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
58+
import FollowUpList from '@/views/crm/followup/index.vue'
59+
import { BizTypeEnum } from '@/api/crm/permission'
60+
import type { OperateLogV2VO } from '@/api/system/operatelog'
61+
import { getOperateLogPage } from '@/api/crm/operateLog'
62+
63+
defineOptions({ name: 'CrmClueDetail' })
64+
65+
const clueId = ref(0) // 线索编号
66+
const loading = ref(true) // 加载中
67+
const message = useMessage() // 消息弹窗
68+
const { delView } = useTagsViewStore() // 视图操作
69+
const { currentRoute } = useRouter() // 路由
70+
71+
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref
72+
73+
/** 获取详情 */
74+
const clue = ref<ClueApi.ClueVO>({} as ClueApi.ClueVO) // 线索详情
75+
const getClue = async () => {
76+
loading.value = true
77+
try {
78+
clue.value = await ClueApi.getClue(clueId.value)
79+
await getOperateLog()
80+
} finally {
81+
loading.value = false
82+
}
83+
}
84+
85+
/** 编辑线索 */
86+
const formRef = ref<InstanceType<typeof ClueForm>>() // 线索表单 Ref
87+
const openForm = () => {
88+
formRef.value?.open('update', clueId.value)
89+
}
90+
91+
/** 线索转移 */
92+
const transferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 线索转移表单 ref
93+
const transfer = () => {
94+
transferFormRef.value?.open('线索转移', clueId.value, ClueApi.transferClue)
95+
}
96+
97+
/** 转化为客户 */
98+
const handleTransform = async () => {
99+
await message.confirm(`确定将【${clue.value.name}】转化为客户吗?`)
100+
await ClueApi.transformClue([clueId.value])
101+
message.success(`转化客户【${clue.value.name}】成功`)
102+
await getClue()
103+
}
104+
105+
/** 获取操作日志 */
106+
const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表
107+
const getOperateLog = async () => {
108+
const data = await getOperateLogPage({
109+
bizType: BizTypeEnum.CRM_CLUE,
110+
bizId: clueId.value
111+
})
112+
logList.value = data.list
113+
}
114+
115+
const close = () => {
116+
delView(unref(currentRoute))
117+
}
118+
119+
/** 初始化 */
120+
const { params } = useRoute()
121+
onMounted(() => {
122+
if (!params.id) {
123+
message.warning('参数错误,线索不能为空!')
124+
close()
125+
return
126+
}
127+
clueId.value = params.id as unknown as number
128+
getClue()
129+
})
130+
</script>

0 commit comments

Comments
 (0)