Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
admin
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
d29dfef7
authored
Jan 14, 2024
by
puhui999
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
新增客户跟进
parent
1e68cd53
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
610 additions
and
53 deletions
+610
-53
src/api/crm/business/index.ts
+5
-7
src/api/crm/contact/index.ts
+6
-2
src/api/crm/followup/index.ts
+54
-0
src/components/OperateLogV2/src/OperateLogV2.vue
+2
-2
src/utils/dict.ts
+4
-3
src/views/crm/customer/detail/index.vue
+12
-11
src/views/crm/customer/index.vue
+10
-28
src/views/crm/followup/FollowUpRecordForm.vue
+136
-0
src/views/crm/followup/components/BusinessList.vue
+71
-0
src/views/crm/followup/components/BusinessListSelectForm.vue
+79
-0
src/views/crm/followup/components/ContactList.vue
+92
-0
src/views/crm/followup/components/index.ts
+4
-0
src/views/crm/followup/index.vue
+135
-0
No files found.
src/api/crm/business/index.ts
View file @
d29dfef7
/*
* @Author: zyna
* @Date: 2023-12-02 13:08:56
* @LastEditTime: 2023-12-17 16:28:20
* @FilePath: \yudao-ui-admin-vue3\src\api\crm\business\index.ts
* @Description:
*/
import
request
from
'@/config/axios'
export
interface
BusinessVO
{
...
...
@@ -67,3 +60,8 @@ export const exportBusiness = async (params) => {
export
const
getBusinessPageByContact
=
async
(
params
)
=>
{
return
await
request
.
get
({
url
:
`/crm/business/page-by-contact`
,
params
})
}
// 获得 CRM 商机列表
export
const
getBusinessListByIds
=
async
(
val
:
number
[])
=>
{
return
await
request
.
get
({
url
:
'/crm/business/list-by-ids'
,
params
:
{
ids
:
val
}
})
}
src/api/crm/contact/index.ts
View file @
d29dfef7
...
...
@@ -71,6 +71,11 @@ export const getSimpleContactList = async () => {
return
await
request
.
get
({
url
:
`/crm/contact/simple-all-list`
})
}
// 获得 CRM 联系人列表
export
const
getContactListByIds
=
async
(
val
:
number
[])
=>
{
return
await
request
.
get
({
url
:
'/crm/contact/list-by-ids'
,
params
:
{
ids
:
val
}
})
}
// 批量新增联系人商机关联
export
const
createContactBusinessList
=
async
(
data
:
ContactBusinessReqVO
)
=>
{
return
await
request
.
post
({
url
:
`/crm/contact/create-business-list`
,
data
})
...
...
@@ -84,4 +89,4 @@ export const deleteContactBusinessList = async (data: ContactBusinessReqVO) => {
// 查询联系人操作日志
export
const
getOperateLogPage
=
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
'/crm/contact/operate-log-page'
,
params
})
}
\ No newline at end of file
}
src/api/crm/followup/index.ts
0 → 100644
View file @
d29dfef7
import
request
from
'@/config/axios'
// 跟进记录 VO
export
interface
FollowUpRecordVO
{
// 编号
id
:
number
// 数据类型
bizType
:
number
// 数据编号
bizId
:
number
// 跟进类型
type
:
number
// 跟进内容
content
:
string
// 下次联系时间
nextTime
:
Date
// 关联的商机编号数组
businessIds
:
number
[]
// 关联的联系人编号数组
contactIds
:
number
[]
}
// 跟进记录 API
export
const
FollowUpRecordApi
=
{
// 查询跟进记录分页
getFollowUpRecordPage
:
async
(
params
:
any
)
=>
{
return
await
request
.
get
({
url
:
`/crm/follow-up-record/page`
,
params
})
},
// 查询跟进记录详情
getFollowUpRecord
:
async
(
id
:
number
)
=>
{
return
await
request
.
get
({
url
:
`/crm/follow-up-record/get?id=`
+
id
})
},
// 新增跟进记录
createFollowUpRecord
:
async
(
data
:
FollowUpRecordVO
)
=>
{
return
await
request
.
post
({
url
:
`/crm/follow-up-record/create`
,
data
})
},
// 修改跟进记录
updateFollowUpRecord
:
async
(
data
:
FollowUpRecordVO
)
=>
{
return
await
request
.
put
({
url
:
`/crm/follow-up-record/update`
,
data
})
},
// 删除跟进记录
deleteFollowUpRecord
:
async
(
id
:
number
)
=>
{
return
await
request
.
delete
({
url
:
`/crm/follow-up-record/delete?id=`
+
id
})
},
// 导出跟进记录 Excel
exportFollowUpRecord
:
async
(
params
)
=>
{
return
await
request
.
download
({
url
:
`/crm/follow-up-record/export-excel`
,
params
})
}
}
src/components/OperateLogV2/src/OperateLogV2.vue
View file @
d29dfef7
<
template
>
<!-- TODO @puhui999:左边不用有空隙哈 -->
<div
class=
"p-20px"
>
<div
class=
"p
t
-20px"
>
<el-timeline>
<el-timeline-item
v-for=
"(log, index) in logList"
...
...
@@ -58,7 +58,7 @@ const getUserTypeColor = (type: number) => {
<
style
lang=
"scss"
scoped
>
//
时间线样式调整
:deep
(
.el-timeline
)
{
margin
:
10px
0
0
1
6
0px
;
margin
:
10px
0
0
1
1
0px
;
.el-timeline-item__wrapper
{
position
:
relative
;
...
...
src/utils/dict.ts
View file @
d29dfef7
/**
* 数据字典工具类
*/
import
{
useDictStoreWithOut
}
from
'@/store/modules/dict'
import
{
ElementPlusInfoType
}
from
'@/types/elementPlus'
import
{
useDictStoreWithOut
}
from
'@/store/modules/dict'
import
{
ElementPlusInfoType
}
from
'@/types/elementPlus'
const
dictStore
=
useDictStoreWithOut
()
...
...
@@ -205,5 +205,6 @@ export enum DICT_TYPE {
CRM_CUSTOMER_SOURCE
=
'crm_customer_source'
,
CRM_PRODUCT_STATUS
=
'crm_product_status'
,
CRM_PERMISSION_LEVEL
=
'crm_permission_level'
,
// CRM 数据权限的级别
CRM_PRODUCT_UNIT
=
'crm_product_unit'
// 产品单位
CRM_PRODUCT_UNIT
=
'crm_product_unit'
,
// 产品单位
CRM_FOLLOW_UP_TYPE
=
'crm_follow_up_type'
// 跟进方式
}
src/views/crm/customer/detail/index.vue
View file @
d29dfef7
<
template
>
<CustomerDetailsHeader
:customer=
"customer"
:loading=
"loading"
>
<!-- @puhui999:返回是不是可以去掉哈,貌似用途可能不大 -->
<el-button
@
click=
"close"
>
返回
</el-button>
<!-- TODO puhui999: 按钮数据权限收尾统一完善,需要按权限分级和客户状态来动态显示匹配的按钮 -->
<el-button
v-hasPermi=
"['crm:customer:update']"
type=
"primary"
@
click=
"openForm"
>
编辑
...
...
@@ -12,8 +10,10 @@
<el-button>
更改成交状态
</el-button>
<el-button
v-if=
"customer.lockStatus"
@
click=
"handleUnlock"
>
解锁
</el-button>
<el-button
v-if=
"!customer.lockStatus"
@
click=
"handleLock"
>
锁定
</el-button>
<el-button
v-if=
"!customer.ownerUserId"
type=
"primary"
@
click=
"receive"
>
领取客户
</el-button>
<el-button
v-if=
"customer.ownerUserId"
@
click=
"putPool"
>
客户放入公海
</el-button>
<el-button
v-if=
"!customer.ownerUserId"
type=
"primary"
@
click=
"handleReceive"
>
领取客户
</el-button>
<el-button
v-if=
"customer.ownerUserId"
@
click=
"handlePutPool"
>
客户放入公海
</el-button>
</CustomerDetailsHeader>
<el-col>
<el-tabs>
...
...
@@ -23,6 +23,9 @@
<el-tab-pane
label=
"操作日志"
>
<OperateLogV2
:log-list=
"logList"
/>
</el-tab-pane>
<el-tab-pane
label=
"跟进"
>
<FollowUpList
:biz-id=
"customerId"
:biz-type=
"BizTypeEnum.CRM_CUSTOMER"
/>
</el-tab-pane>
<el-tab-pane
label=
"联系人"
lazy
>
<ContactList
:biz-id=
"customer.id!"
:biz-type=
"BizTypeEnum.CRM_CUSTOMER"
/>
</el-tab-pane>
...
...
@@ -58,6 +61,7 @@ import BusinessList from '@/views/crm/business/components/BusinessList.vue' //
import
ReceivableList
from
'@/views/crm/receivable/components/ReceivableList.vue'
// 回款列表
import
ReceivablePlanList
from
'@/views/crm/receivable/plan/components/ReceivablePlanList.vue'
// 回款计划列表
import
PermissionList
from
'@/views/crm/permission/components/PermissionList.vue'
// 团队成员列表(权限)
import
FollowUpList
from
'@/views/crm/followup/index.vue'
import
{
BizTypeEnum
}
from
'@/api/crm/permission'
import
type
{
OperateLogV2VO
}
from
'@/api/system/operatelog'
...
...
@@ -67,7 +71,7 @@ const customerId = ref(0) // 客户编号
const
loading
=
ref
(
true
)
// 加载中
const
message
=
useMessage
()
// 消息弹窗
const
{
delView
}
=
useTagsViewStore
()
// 视图操作
const
{
currentRoute
,
push
}
=
useRouter
()
// 路由
const
{
currentRoute
}
=
useRouter
()
// 路由
/** 获取详情 */
const
customer
=
ref
<
CustomerApi
.
CustomerVO
>
({}
as
CustomerApi
.
CustomerVO
)
// 客户详情
...
...
@@ -106,9 +110,8 @@ const handleUnlock = async () => {
await
getCustomer
()
}
// TODO @puhui999:下面两个方法的命名,也用 handleXXX 风格哈
/** 领取客户 */
const
r
eceive
=
async
()
=>
{
const
handleR
eceive
=
async
()
=>
{
await
message
.
confirm
(
`确定领取客户【
${
customer
.
value
.
name
}
】 吗?`
)
await
CustomerApi
.
receive
([
unref
(
customerId
.
value
)])
message
.
success
(
`领取客户【
${
customer
.
value
.
name
}
】成功`
)
...
...
@@ -116,7 +119,7 @@ const receive = async () => {
}
/** 客户放入公海 */
const
p
utPool
=
async
()
=>
{
const
handleP
utPool
=
async
()
=>
{
await
message
.
confirm
(
`确定将客户【
${
customer
.
value
.
name
}
】放入公海吗?`
)
await
CustomerApi
.
putPool
(
unref
(
customerId
.
value
))
message
.
success
(
`客户【
${
customer
.
value
.
name
}
】放入公海成功`
)
...
...
@@ -135,15 +138,13 @@ const getOperateLog = async () => {
const
close
=
()
=>
{
delView
(
unref
(
currentRoute
))
// TODO 先返回到客户列表
push
({
name
:
'CrmCustomer'
})
}
/** 初始化 */
const
{
params
}
=
useRoute
()
onMounted
(()
=>
{
if
(
!
params
.
id
)
{
ElM
essage
.
warning
(
'参数错误,客户不能为空!'
)
m
essage
.
warning
(
'参数错误,客户不能为空!'
)
close
()
return
}
...
...
src/views/crm/customer/index.vue
View file @
d29dfef7
...
...
@@ -100,14 +100,10 @@
<!-- 列表 -->
<ContentWrap>
<!-- TODO @puhui999:是不是就 3 重呀,我负责的,我参与的,我下属的 -->
<el-tabs
v-model=
"activeName"
@
tab-click=
"handleClick"
>
<el-tab-pane
label=
"客户列表"
name=
"1"
/>
<el-tab-pane
label=
"我负责的"
name=
"2"
/>
<el-tab-pane
label=
"我关注的"
name=
"3"
/>
<el-tab-pane
label=
"我参与的"
name=
"4"
/>
<el-tab-pane
label=
"下属负责的"
name=
"5"
/>
<el-tab-pane
label=
"客户公海"
name=
"6"
/>
<el-tab-pane
label=
"我负责的"
name=
"1"
/>
<el-tab-pane
label=
"我参与的"
name=
"2"
/>
<el-tab-pane
label=
"下属负责的"
name=
"3"
/>
</el-tabs>
<el-table
v-loading=
"loading"
:data=
"list"
:show-overflow-tooltip=
"true"
:stripe=
"true"
>
<el-table-column
align=
"center"
label=
"编号"
prop=
"id"
/>
...
...
@@ -149,7 +145,9 @@
<dict-tag
:type=
"DICT_TYPE.INFRA_BOOLEAN_STRING"
:value=
"scope.row.dealStatus"
/>
</
template
>
</el-table-column>
<!-- TODO @puhui999:距进入公海天数 -->
<el-table-column
align=
"center"
label=
"距离进入公海"
prop=
"poolDay"
>
<
template
#
default=
"scope"
>
{{
scope
.
row
.
poolDay
}}
天
</
template
>
</el-table-column>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
...
...
@@ -251,43 +249,27 @@ const activeName = ref('1') // 列表 tab
enum
CrmSceneTypeEnum
{
OWNER
=
1
,
FOLLOW
=
2
,
INVOLVED
=
3
,
SUBORDINATE
=
4
INVOLVED
=
2
,
SUBORDINATE
=
3
}
const
handleClick
=
(
tab
:
TabsPaneContext
)
=>
{
switch
(
tab
.
paneName
)
{
case
'1'
:
resetQuery
()
break
case
'2'
:
resetQuery
(()
=>
{
queryParams
.
value
.
sceneType
=
CrmSceneTypeEnum
.
OWNER
})
break
case
'3'
:
resetQuery
(()
=>
{
queryParams
.
value
.
sceneType
=
CrmSceneTypeEnum
.
FOLLOW
})
break
// TODO @puhui999:这个貌似报错?
case
'4'
:
case
'2'
:
resetQuery
(()
=>
{
queryParams
.
value
.
sceneType
=
CrmSceneTypeEnum
.
INVOLVED
})
break
case
'
5
'
:
case
'
3
'
:
resetQuery
(()
=>
{
queryParams
.
value
.
sceneType
=
CrmSceneTypeEnum
.
SUBORDINATE
})
break
// TODO @puhui999:公海单独一个菜单哈。
case
'6'
:
resetQuery
(()
=>
{
queryParams
.
value
.
pool
=
true
})
break
}
}
...
...
src/views/crm/followup/FollowUpRecordForm.vue
0 → 100644
View file @
d29dfef7
<
template
>
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
width=
"50%"
>
<el-form
ref=
"formRef"
v-loading=
"formLoading"
:model=
"formData"
:rules=
"formRules"
label-width=
"120px"
>
<el-row>
<el-col
:span=
"12"
>
<el-form-item
label=
"跟进类型"
prop=
"type"
>
<el-select
v-model=
"formData.type"
placeholder=
"请选择跟进类型"
>
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_UP_TYPE)"
:key=
"dict.value"
:label=
"dict.label"
:value=
"dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col
:span=
"12"
>
<el-form-item
label=
"下次联系时间"
prop=
"nextTime"
>
<el-date-picker
v-model=
"formData.nextTime"
placeholder=
"选择下次联系时间"
type=
"date"
value-format=
"x"
/>
</el-form-item>
</el-col>
<el-col
:span=
"24"
>
<el-form-item
label=
"跟进内容"
prop=
"content"
>
<Editor
v-model=
"formData.content"
height=
"300px"
/>
</el-form-item>
</el-col>
<el-col
:span=
"24"
>
<el-form-item
label=
"关联联系人"
prop=
"contactIds"
>
<el-button
@
click=
"submitForm"
>
<Icon
class=
"mr-5px"
icon=
"ep:plus"
/>
选择添加联系人
</el-button>
<contact-list
v-model:contactIds=
"formData.contactIds"
/>
</el-form-item>
</el-col>
<el-col
:span=
"24"
>
<el-form-item
label=
"关联商机"
prop=
"businessIds"
>
<el-button
@
click=
"submitForm"
>
<Icon
class=
"mr-5px"
icon=
"ep:plus"
/>
选择添加商机
</el-button>
<business-list
v-model:businessIds=
"formData.businessIds"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template
#
footer
>
<el-button
:disabled=
"formLoading"
type=
"primary"
@
click=
"submitForm"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
</template>
<
script
lang=
"ts"
setup
>
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'@/utils/dict'
import
{
FollowUpRecordApi
,
FollowUpRecordVO
}
from
'@/api/crm/followup'
import
{
BusinessList
,
ContactList
}
from
'./components'
/** 跟进记录 表单 */
defineOptions
({
name
:
'FollowUpRecordForm'
})
const
{
t
}
=
useI18n
()
// 国际化
const
message
=
useMessage
()
// 消息弹窗
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
dialogTitle
=
ref
(
''
)
// 弹窗的标题
const
formLoading
=
ref
(
false
)
// 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const
formType
=
ref
(
''
)
// 表单的类型:create - 新增;update - 修改
const
formData
=
ref
<
FollowUpRecordVO
>
({}
as
FollowUpRecordVO
)
const
formRules
=
reactive
({
type
:
[{
required
:
true
,
message
:
'跟进类型不能为空'
,
trigger
:
'change'
}],
content
:
[{
required
:
true
,
message
:
'跟进内容不能为空'
,
trigger
:
'blur'
}],
nextTime
:
[{
required
:
true
,
message
:
'下次联系时间不能为空'
,
trigger
:
'blur'
}]
})
const
formRef
=
ref
()
// 表单 Ref
/** 打开弹窗 */
const
open
=
async
(
bizType
:
number
,
bizId
:
number
,
type
:
string
,
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
dialogTitle
.
value
=
t
(
'action.'
+
type
)
formType
.
value
=
type
resetForm
()
formData
.
value
.
bizType
=
bizType
formData
.
value
.
bizId
=
bizId
// 修改时,设置数据
if
(
id
)
{
formLoading
.
value
=
true
try
{
formData
.
value
=
await
FollowUpRecordApi
.
getFollowUpRecord
(
id
)
}
finally
{
formLoading
.
value
=
false
}
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
/** 提交表单 */
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
submitForm
=
async
()
=>
{
// 校验表单
await
formRef
.
value
.
validate
()
// 提交请求
formLoading
.
value
=
true
try
{
const
data
=
formData
.
value
as
unknown
as
FollowUpRecordVO
if
(
formType
.
value
===
'create'
)
{
await
FollowUpRecordApi
.
createFollowUpRecord
(
data
)
message
.
success
(
t
(
'common.createSuccess'
))
}
else
{
await
FollowUpRecordApi
.
updateFollowUpRecord
(
data
)
message
.
success
(
t
(
'common.updateSuccess'
))
}
dialogVisible
.
value
=
false
// 发送操作成功的事件
emit
(
'success'
)
}
finally
{
formLoading
.
value
=
false
}
}
/** 重置表单 */
const
resetForm
=
()
=>
{
formRef
.
value
?.
resetFields
()
formData
.
value
=
{}
as
FollowUpRecordVO
}
</
script
>
src/views/crm/followup/components/BusinessList.vue
0 → 100644
View file @
d29dfef7
<
template
>
<el-table
:data=
"list"
:show-overflow-tooltip=
"true"
:stripe=
"true"
height=
"200"
>
<el-table-column
align=
"center"
label=
"商机名称"
prop=
"name"
/>
<el-table-column
align=
"center"
label=
"客户名称"
prop=
"customerName"
/>
<el-table-column
align=
"center"
label=
"商机金额"
prop=
"price"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"预计成交日期"
prop=
"dealTime"
width=
"120px"
/>
<el-table-column
align=
"center"
label=
"商机状态类型"
prop=
"statusTypeName"
width=
"120"
/>
<el-table-column
align=
"center"
label=
"商机状态"
prop=
"statusName"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"更新时间"
prop=
"updateTime"
width=
"180px"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"创建时间"
prop=
"createTime"
width=
"180px"
/>
<el-table-column
align=
"center"
label=
"负责人"
prop=
"ownerUserName"
width=
"120"
/>
<el-table-column
align=
"center"
label=
"创建人"
prop=
"creatorName"
width=
"120"
/>
<el-table-column
align=
"center"
label=
"备注"
prop=
"remark"
/>
<el-table-column
align=
"center"
fixed=
"right"
label=
"操作"
width=
"130"
>
<template
#
default=
"scope"
>
<el-button
link
type=
"danger"
@
click=
"handleDelete(scope.row.id)"
>
移除
</el-button>
</
template
>
</el-table-column>
</el-table>
</template>
<
script
lang=
"ts"
setup
>
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
*
as
BusinessApi
from
'@/api/crm/business'
defineOptions
({
name
:
'BusinessList'
})
const
props
=
withDefaults
(
defineProps
<
{
businessIds
:
number
[]
}
>
(),
{
businessIds
:
()
=>
[]
})
const
list
=
ref
<
BusinessApi
.
BusinessVO
[]
>
([]
as
BusinessApi
.
BusinessVO
[])
watch
(
()
=>
props
.
businessIds
,
(
val
)
=>
{
if
(
!
val
||
val
.
length
===
0
)
{
return
}
list
.
value
=
BusinessApi
.
getBusinessListByIds
(
val
)
as
unknown
as
BusinessApi
.
BusinessVO
[]
}
)
const
emits
=
defineEmits
<
{
(
e
:
'update:businessIds'
,
businessIds
:
number
[]):
void
}
>
()
const
handleDelete
=
(
id
:
number
)
=>
{
const
index
=
list
.
value
.
findIndex
((
item
)
=>
item
.
id
===
id
)
if
(
index
!==
-
1
)
{
list
.
value
.
splice
(
index
,
1
)
}
emits
(
'update:businessIds'
,
list
.
value
.
map
((
item
)
=>
item
.
id
)
)
}
</
script
>
src/views/crm/followup/components/BusinessListSelectForm.vue
0 → 100644
View file @
d29dfef7
<
template
>
<Dialog
v-model=
"dialogVisible"
:title=
"dialogTitle"
width=
"50%"
>
<el-row>
<el-col
:span=
"12"
>
<el-form-item
label=
"跟进类型"
prop=
"type"
>
<el-select
v-model=
"formData.type"
placeholder=
"请选择跟进类型"
>
<el-option
v-for=
"dict in getIntDictOptions(DICT_TYPE.CRM_FOLLOW_UP_TYPE)"
:key=
"dict.value"
:label=
"dict.label"
:value=
"dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col
:span=
"12"
>
<el-form-item
label=
"下次联系时间"
prop=
"nextTime"
>
<el-date-picker
v-model=
"formData.nextTime"
placeholder=
"选择下次联系时间"
type=
"date"
value-format=
"x"
/>
</el-form-item>
</el-col>
</el-row>
<template
#
footer
>
<el-button
:disabled=
"formLoading"
type=
"primary"
@
click=
"submitForm"
>
确 定
</el-button>
<el-button
@
click=
"dialogVisible = false"
>
取 消
</el-button>
</
template
>
</Dialog>
</template>
<
script
lang=
"ts"
setup
>
/** 跟进记录 表单 */
defineOptions
({
name
:
'BusinessListSelectForm'
})
const
dialogVisible
=
ref
(
false
)
// 弹窗的是否展示
const
dialogTitle
=
ref
(
''
)
// 弹窗的标题
const
formLoading
=
ref
(
false
)
// 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const
formData
=
ref
([])
/** 打开弹窗 */
const
open
=
async
(
type
:
string
,
id
?:
number
)
=>
{
dialogVisible
.
value
=
true
dialogTitle
.
value
=
t
(
'action.'
+
type
)
formType
.
value
=
type
resetForm
()
// 修改时,设置数据
if
(
id
)
{
formLoading
.
value
=
true
try
{
formData
.
value
=
await
FollowUpRecordApi
.
getFollowUpRecord
(
id
)
}
finally
{
formLoading
.
value
=
false
}
}
}
defineExpose
({
open
})
// 提供 open 方法,用于打开弹窗
/** 提交表单 */
const
emit
=
defineEmits
([
'success'
])
// 定义 success 事件,用于操作成功后的回调
const
submitForm
=
async
()
=>
{
// 校验表单
await
formRef
.
value
.
validate
()
// 提交请求
formLoading
.
value
=
true
try
{
// 发送操作成功的事件
emit
(
'success'
)
}
finally
{
formLoading
.
value
=
false
}
}
/** 重置表单 */
const
resetForm
=
()
=>
{
formRef
.
value
?.
resetFields
()
}
</
script
>
src/views/crm/followup/components/ContactList.vue
0 → 100644
View file @
d29dfef7
<
template
>
<el-table
:data=
"list"
:show-overflow-tooltip=
"true"
:stripe=
"true"
height=
"200"
>
<el-table-column
align=
"center"
fixed=
"left"
label=
"姓名"
prop=
"name"
width=
"140"
/>
<el-table-column
align=
"center"
fixed=
"left"
label=
"客户名称"
prop=
"customerName"
width=
"120"
/>
<el-table-column
align=
"center"
label=
"手机"
prop=
"mobile"
width=
"120"
/>
<el-table-column
align=
"center"
label=
"电话"
prop=
"telephone"
width=
"120"
/>
<el-table-column
align=
"center"
label=
"邮箱"
prop=
"email"
width=
"120"
/>
<el-table-column
align=
"center"
label=
"职位"
prop=
"post"
width=
"120"
/>
<el-table-column
align=
"center"
label=
"地址"
prop=
"detailAddress"
width=
"120"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"下次联系时间"
prop=
"contactNextTime"
width=
"180px"
/>
<el-table-column
align=
"center"
label=
"关键决策人"
prop=
"master"
width=
"100"
>
<template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.INFRA_BOOLEAN_STRING"
:value=
"scope.row.master"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"直属上级"
prop=
"parentName"
width=
"140"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"最后跟进时间"
prop=
"contactLastTime"
width=
"180px"
/>
<el-table-column
align=
"center"
label=
"性别"
prop=
"sex"
>
<
template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.SYSTEM_USER_SEX"
:value=
"scope.row.sex"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"负责人"
prop=
"ownerUserName"
width=
"120"
/>
<el-table-column
align=
"center"
label=
"创建人"
prop=
"creatorName"
width=
"120"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"更新时间"
prop=
"updateTime"
width=
"180px"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"创建时间"
prop=
"createTime"
width=
"180px"
/>
<el-table-column
align=
"center"
label=
"备注"
prop=
"remark"
/>
<el-table-column
align=
"center"
fixed=
"right"
label=
"操作"
width=
"130"
>
<
template
#
default=
"scope"
>
<el-button
link
type=
"danger"
@
click=
"handleDelete(scope.row.id)"
>
移除
</el-button>
</
template
>
</el-table-column>
</el-table>
</template>
<
script
lang=
"ts"
setup
>
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
DICT_TYPE
}
from
'@/utils/dict'
import
*
as
ContactApi
from
'@/api/crm/contact'
defineOptions
({
name
:
'ContactList'
})
const
props
=
withDefaults
(
defineProps
<
{
contactIds
:
number
[]
}
>
(),
{
contactIds
:
()
=>
[]
})
const
list
=
ref
<
ContactApi
.
ContactVO
[]
>
([]
as
ContactApi
.
ContactVO
[])
watch
(
()
=>
props
.
contactIds
,
(
val
)
=>
{
if
(
!
val
||
val
.
length
===
0
)
{
return
}
list
.
value
=
ContactApi
.
getContactListByIds
(
val
)
as
unknown
as
ContactApi
.
ContactVO
[]
}
)
const
emits
=
defineEmits
<
{
(
e
:
'update:contactIds'
,
contactIds
:
number
[]):
void
}
>
()
const
handleDelete
=
(
id
:
number
)
=>
{
const
index
=
list
.
value
.
findIndex
((
item
)
=>
item
.
id
===
id
)
if
(
index
!==
-
1
)
{
list
.
value
.
splice
(
index
,
1
)
}
emits
(
'update:contactIds'
,
list
.
value
.
map
((
item
)
=>
item
.
id
)
)
}
</
script
>
src/views/crm/followup/components/index.ts
0 → 100644
View file @
d29dfef7
import
BusinessList
from
'./BusinessList.vue'
import
ContactList
from
'./ContactList.vue'
export
{
BusinessList
,
ContactList
}
src/views/crm/followup/index.vue
0 → 100644
View file @
d29dfef7
<
template
>
<!-- 操作栏 -->
<el-row
class=
"mb-10px"
justify=
"end"
>
<el-button
@
click=
"openForm('create')"
>
<Icon
class=
"mr-5px"
icon=
"ep:edit"
/>
写跟进
</el-button>
</el-row>
<!-- 列表 -->
<ContentWrap>
<el-table
v-loading=
"loading"
:data=
"list"
:show-overflow-tooltip=
"true"
:stripe=
"true"
>
<el-table-column
align=
"center"
label=
"编号"
prop=
"id"
/>
<el-table-column
align=
"center"
label=
"跟进人"
prop=
"creatorName"
/>
<el-table-column
align=
"center"
label=
"跟进类型"
prop=
"type"
>
<template
#
default=
"scope"
>
<dict-tag
:type=
"DICT_TYPE.CRM_FOLLOW_UP_TYPE"
:value=
"scope.row.type"
/>
</
template
>
</el-table-column>
<el-table-column
align=
"center"
label=
"跟进内容"
prop=
"content"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"下次联系时间"
prop=
"nextTime"
width=
"180px"
/>
<el-table-column
align=
"center"
label=
"关联联系人"
prop=
"contactIds"
/>
<el-table-column
align=
"center"
label=
"关联商机"
prop=
"businessIds"
/>
<el-table-column
:formatter=
"dateFormatter"
align=
"center"
label=
"创建时间"
prop=
"createTime"
width=
"180px"
/>
<el-table-column
align=
"center"
label=
"操作"
>
<
template
#
default=
"scope"
>
<el-button
v-hasPermi=
"['crm:follow-up-record:update']"
link
type=
"primary"
@
click=
"openForm('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPermi=
"['crm:follow-up-record:delete']"
link
type=
"danger"
@
click=
"handleDelete(scope.row.id)"
>
删除
</el-button>
</
template
>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
v-model:limit=
"queryParams.pageSize"
v-model:page=
"queryParams.pageNo"
:total=
"total"
@
pagination=
"getList"
/>
</ContentWrap>
<!-- 表单弹窗:添加/修改 -->
<FollowUpRecordForm
ref=
"formRef"
@
success=
"getList"
/>
</template>
<
script
lang=
"ts"
setup
>
import
{
dateFormatter
}
from
'@/utils/formatTime'
import
{
DICT_TYPE
}
from
'@/utils/dict'
import
{
FollowUpRecordApi
,
FollowUpRecordVO
}
from
'@/api/crm/followup'
import
FollowUpRecordForm
from
'./FollowUpRecordForm.vue'
/** 跟进记录 列表 */
defineOptions
({
name
:
'FollowUpRecord'
})
const
props
=
defineProps
<
{
bizType
:
number
bizId
:
number
}
>
()
const
message
=
useMessage
()
// 消息弹窗
const
{
t
}
=
useI18n
()
// 国际化
const
loading
=
ref
(
true
)
// 列表的加载中
const
list
=
ref
<
FollowUpRecordVO
[]
>
([])
// 列表的数据
// 列表的总页数
const
total
=
ref
(
0
)
const
queryParams
=
reactive
({
pageNo
:
1
,
pageSize
:
10
,
bizType
:
0
,
bizId
:
0
})
/** 查询列表 */
const
getList
=
async
()
=>
{
loading
.
value
=
true
try
{
const
data
=
await
FollowUpRecordApi
.
getFollowUpRecordPage
(
queryParams
)
list
.
value
=
data
.
list
total
.
value
=
data
.
total
}
finally
{
loading
.
value
=
false
}
}
/** 添加/修改操作 */
const
formRef
=
ref
<
InstanceType
<
typeof
FollowUpRecordForm
>>
()
const
openForm
=
(
type
:
string
,
id
?:
number
)
=>
{
formRef
.
value
?.
open
(
props
.
bizType
,
props
.
bizId
,
type
,
id
)
}
/** 删除按钮操作 */
const
handleDelete
=
async
(
id
:
number
)
=>
{
try
{
// 删除的二次确认
await
message
.
delConfirm
()
// 发起删除
await
FollowUpRecordApi
.
deleteFollowUpRecord
(
id
)
message
.
success
(
t
(
'common.delSuccess'
))
// 刷新列表
await
getList
()
}
catch
{}
}
watch
(
()
=>
props
.
bizId
,
()
=>
{
queryParams
.
bizType
=
props
.
bizType
queryParams
.
bizId
=
props
.
bizId
getList
()
}
)
</
script
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment