Commit 783f6283 by dhb52

Merge remote-tracking branch 'yudao/dev' into dev

parents 094b1925 154f84a8
import request from '@/config/axios' import request from '@/config/axios'
// TODO 芋艿:融合下 // TODO 芋艿:融合下
// 3. 获得分配给我的客户数量
export const getFollowCustomerCount = async () => {
return await request.get({ url: '/crm/customer/follow-customer-count' })
}
// 5. 获得待审核合同数量 // 5. 获得待审核合同数量
export const getCheckContractCount = async () => { export const getCheckContractCount = async () => {
return await request.get({ url: '/crm/contract/check-contract-count' }) return await request.get({ url: '/crm/contract/check-contract-count' })
......
...@@ -4,22 +4,42 @@ import { TransferReqVO } from '@/api/crm/customer' ...@@ -4,22 +4,42 @@ import { TransferReqVO } from '@/api/crm/customer'
export interface BusinessVO { export interface BusinessVO {
id: number id: number
name: string name: string
customerId: number
customerName?: string
followUpStatus: boolean
contactLastTime: Date
contactNextTime: Date
ownerUserId: number
ownerUserName?: string // 负责人的用户名称
ownerUserDept?: string // 负责人的部门名称
statusTypeId: number statusTypeId: number
statusTypeName?: string
statusId: number statusId: number
contactNextTime: Date statusName?: string
customerId: number endStatus: number
endRemark: string
dealTime: Date dealTime: Date
price: number totalProductPrice: number
totalPrice: number
discountPercent: number discountPercent: number
productPrice: number
remark: string remark: string
ownerUserId: number creator: string // 创建人
roUserIds: string creatorName?: string // 创建人名称
rwUserIds: string createTime: Date // 创建时间
endStatus: number updateTime: Date // 更新时间
endRemark: string products?: [
contactLastTime: Date {
followUpStatus: number id: number
productId: number
productName: string
productNo: string
productUnit: number
productPrice: number
businessPrice: number
count: number
totalPrice: number
}
]
} }
// 查询 CRM 商机列表 // 查询 CRM 商机列表
...@@ -52,6 +72,11 @@ export const updateBusiness = async (data: BusinessVO) => { ...@@ -52,6 +72,11 @@ export const updateBusiness = async (data: BusinessVO) => {
return await request.put({ url: `/crm/business/update`, data }) return await request.put({ url: `/crm/business/update`, data })
} }
// 修改 CRM 商机状态
export const updateBusinessStatus = async (data: BusinessVO) => {
return await request.put({ url: `/crm/business/update-status`, data })
}
// 删除 CRM 商机 // 删除 CRM 商机
export const deleteBusiness = async (id: number) => { export const deleteBusiness = async (id: number) => {
return await request.delete({ url: `/crm/business/delete?id=` + id }) return await request.delete({ url: `/crm/business/delete?id=` + id })
...@@ -67,11 +92,6 @@ export const getBusinessPageByContact = async (params) => { ...@@ -67,11 +92,6 @@ export const getBusinessPageByContact = async (params) => {
return await request.get({ url: `/crm/business/page-by-contact`, 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.join(',') } })
}
// 商机转移 // 商机转移
export const transferBusiness = async (data: TransferReqVO) => { export const transferBusiness = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/business/transfer', data }) return await request.put({ url: '/crm/business/transfer', data })
......
import request from '@/config/axios'
export interface BusinessStatusTypeVO {
id: number
name: string
deptIds: number[]
statuses?: {
id: number
name: string
percent: number
}
}
export const DEFAULT_STATUSES = [
{
endStatus: 1,
key: '结束',
name: '赢单',
percent: 100
},
{
endStatus: 2,
key: '结束',
name: '输单',
percent: 0
},
{
endStatus: 3,
key: '结束',
name: '无效',
percent: 0
}
]
// 查询商机状态组列表
export const getBusinessStatusPage = async (params: any) => {
return await request.get({ url: `/crm/business-status/page`, params })
}
// 新增商机状态组
export const createBusinessStatus = async (data: BusinessStatusTypeVO) => {
return await request.post({ url: `/crm/business-status/create`, data })
}
// 修改商机状态组
export const updateBusinessStatus = async (data: BusinessStatusTypeVO) => {
return await request.put({ url: `/crm/business-status/update`, data })
}
// 查询商机状态类型详情
export const getBusinessStatus = async (id: number) => {
return await request.get({ url: `/crm/business-status/get?id=` + id })
}
// 删除商机状态
export const deleteBusinessStatus = async (id: number) => {
return await request.delete({ url: `/crm/business-status/delete?id=` + id })
}
// 获得商机状态组列表
export const getBusinessStatusTypeSimpleList = async () => {
return await request.get({ url: `/crm/business-status/type-simple-list` })
}
// 获得商机阶段列表
export const getBusinessStatusSimpleList = async (typeId: number) => {
return await request.get({ url: `/crm/business-status/status-simple-list`, params: { typeId } })
}
import request from '@/config/axios'
export interface BusinessStatusTypeVO {
id: number
name: string
deptIds: number[]
status: boolean
}
// 查询商机状态类型列表
export const getBusinessStatusTypePage = async (params) => {
return await request.get({ url: `/crm/business-status-type/page`, params })
}
// 查询商机状态类型详情
export const getBusinessStatusType = async (id: number) => {
return await request.get({ url: `/crm/business-status-type/get?id=` + id })
}
// 新增商机状态类型
export const createBusinessStatusType = async (data: BusinessStatusTypeVO) => {
return await request.post({ url: `/crm/business-status-type/create`, data })
}
// 修改商机状态类型
export const updateBusinessStatusType = async (data: BusinessStatusTypeVO) => {
return await request.put({ url: `/crm/business-status-type/update`, data })
}
// 删除商机状态类型
export const deleteBusinessStatusType = async (id: number) => {
return await request.delete({ url: `/crm/business-status-type/delete?id=` + id })
}
// 导出商机状态类型 Excel
export const exportBusinessStatusType = async (params) => {
return await request.download({ url: `/crm/business-status-type/export-excel`, params })
}
// 获取商机状态类型信息列表
export const getBusinessStatusTypeList = async () => {
return await request.get({ url: `/crm/business-status-type/get-simple-list` })
}
// 根据类型ID获取商机状态信息列表
export const getBusinessStatusListByTypeId = async (typeId: number) => {
return await request.get({ url: `/crm/business-status-type/get-status-list?typeId=` + typeId })
}
...@@ -2,29 +2,34 @@ import request from '@/config/axios' ...@@ -2,29 +2,34 @@ import request from '@/config/axios'
import { TransferReqVO } from '@/api/crm/customer' import { TransferReqVO } from '@/api/crm/customer'
export interface ContactVO { export interface ContactVO {
name: string id: number // 编号
nextTime: Date name: string // 联系人名称
mobile: string customerId: number // 客户编号
telephone: string customerName?: string // 客户名称
email: string contactLastTime: Date // 最后跟进时间
post: string contactLastContent: string // 最后跟进内容
customerId: number contactNextTime: Date // 下次联系时间
detailAddress: string ownerUserId: number // 负责人的用户编号
remark: string ownerUserName?: string // 负责人的用户名称
ownerUserId: string ownerUserDept?: string // 负责人的部门名称
lastTime: Date mobile: string // 手机号
id: number telephone: string // 电话
parentId: number qq: string // QQ
qq: number wechat: string // wechat
wechat: string email: string // email
sex: number areaId: number // 所在地
master: boolean areaName?: string // 所在地名称
creatorName: string detailAddress: string // 详细地址
updateTime?: Date sex: number // 性别
createTime?: Date master: boolean // 是否主联系人
customerName: string post: string // 职务
areaName: string parentId: number // 上级联系人编号
ownerUserName: string parentName?: string // 上级联系人名称
remark: string // 备注
creator: string // 创建人
creatorName?: string // 创建人名称
createTime: Date // 创建时间
updateTime: Date // 更新时间
} }
export interface ContactBusinessReqVO { export interface ContactBusinessReqVO {
...@@ -32,6 +37,11 @@ export interface ContactBusinessReqVO { ...@@ -32,6 +37,11 @@ export interface ContactBusinessReqVO {
businessIds: number[] businessIds: number[]
} }
export interface ContactBusiness2ReqVO {
businessId: number
contactIds: number[]
}
// 查询 CRM 联系人列表 // 查询 CRM 联系人列表
export const getContactPage = async (params) => { export const getContactPage = async (params) => {
return await request.get({ url: `/crm/contact/page`, params }) return await request.get({ url: `/crm/contact/page`, params })
...@@ -42,6 +52,11 @@ export const getContactPageByCustomer = async (params: any) => { ...@@ -42,6 +52,11 @@ export const getContactPageByCustomer = async (params: any) => {
return await request.get({ url: `/crm/contact/page-by-customer`, params }) return await request.get({ url: `/crm/contact/page-by-customer`, params })
} }
// 查询 CRM 联系人列表,基于指定商机
export const getContactPageByBusiness = async (params: any) => {
return await request.get({ url: `/crm/contact/page-by-business`, params })
}
// 查询 CRM 联系人详情 // 查询 CRM 联系人详情
export const getContact = async (id: number) => { export const getContact = async (id: number) => {
return await request.get({ url: `/crm/contact/get?id=` + id }) return await request.get({ url: `/crm/contact/get?id=` + id })
...@@ -72,21 +87,26 @@ export const getSimpleContactList = async () => { ...@@ -72,21 +87,26 @@ export const getSimpleContactList = async () => {
return await request.get({ url: `/crm/contact/simple-all-list` }) 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.join(',') } })
}
// 批量新增联系人商机关联 // 批量新增联系人商机关联
export const createContactBusinessList = async (data: ContactBusinessReqVO) => { export const createContactBusinessList = async (data: ContactBusinessReqVO) => {
return await request.post({ url: `/crm/contact/create-business-list`, data }) return await request.post({ url: `/crm/contact/create-business-list`, data })
} }
// 批量新增联系人商机关联
export const createContactBusinessList2 = async (data: ContactBusiness2ReqVO) => {
return await request.post({ url: `/crm/contact/create-business-list2`, data })
}
// 解除联系人商机关联 // 解除联系人商机关联
export const deleteContactBusinessList = async (data: ContactBusinessReqVO) => { export const deleteContactBusinessList = async (data: ContactBusinessReqVO) => {
return await request.delete({ url: `/crm/contact/delete-business-list`, data }) return await request.delete({ url: `/crm/contact/delete-business-list`, data })
} }
// 解除联系人商机关联
export const deleteContactBusinessList2 = async (data: ContactBusiness2ReqVO) => {
return await request.delete({ url: `/crm/contact/delete-business-list2`, data })
}
// 联系人转移 // 联系人转移
export const transferContact = async (data: TransferReqVO) => { export const transferContact = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/contact/transfer', data }) return await request.put({ url: '/crm/contact/transfer', data })
......
import request from '@/config/axios'
export interface ContractConfigVO {
notifyEnabled?: boolean
notifyDays?: number
}
// 获取合同配置
export const getContractConfig = async () => {
return await request.get({ url: `/crm/contract-config/get` })
}
// 更新合同配置
export const saveContractConfig = async (data: ContractConfigVO) => {
return await request.put({ url: `/crm/contract-config/save`, data })
}
import request from '@/config/axios' import request from '@/config/axios'
import { ProductExpandVO } from '@/api/crm/product'
import { TransferReqVO } from '@/api/crm/customer' import { TransferReqVO } from '@/api/crm/customer'
export interface ContractVO { export interface ContractVO {
id: number id: number
name: string name: string
no: string
customerId: number customerId: number
customerName?: string
businessId: number businessId: number
businessName: string businessName: string
contactLastTime: Date
ownerUserId: number
ownerUserName?: string
ownerUserDeptName?: string
processInstanceId: number processInstanceId: number
auditStatus: number
orderDate: Date orderDate: Date
ownerUserId: number
no: string
startTime: Date startTime: Date
endTime: Date endTime: Date
price: number totalProductPrice: number
discountPercent: number discountPercent: number
productPrice: number totalPrice: number
contactId: number totalReceivablePrice: number
signContactId: number
signContactName?: string
signUserId: number signUserId: number
signUserName: string signUserName: string
contactLastTime: Date
auditStatus: number
remark: string remark: string
productItems: ProductExpandVO[] createTime?: Date
creator: string
creatorName: string creatorName: string
updateTime?: Date updateTime?: Date
createTime?: Date products?: [
customerName: string {
contactName: string id: number
ownerUserName: string productId: number
productName: string
productNo: string
productUnit: number
productPrice: number
contractPrice: number
count: number
totalPrice: number
}
]
} }
// 查询 CRM 合同列表 // 查询 CRM 合同列表
...@@ -42,11 +56,23 @@ export const getContractPageByCustomer = async (params: any) => { ...@@ -42,11 +56,23 @@ export const getContractPageByCustomer = async (params: any) => {
return await request.get({ url: `/crm/contract/page-by-customer`, params }) return await request.get({ url: `/crm/contract/page-by-customer`, params })
} }
// 查询 CRM 联系人列表,基于指定商机
export const getContractPageByBusiness = async (params: any) => {
return await request.get({ url: `/crm/contract/page-by-business`, params })
}
// 查询 CRM 合同详情 // 查询 CRM 合同详情
export const getContract = async (id: number) => { export const getContract = async (id: number) => {
return await request.get({ url: `/crm/contract/get?id=` + id }) return await request.get({ url: `/crm/contract/get?id=` + id })
} }
// 查询 CRM 合同下拉列表
export const getContractSimpleList = async (customerId: number) => {
return await request.get({
url: `/crm/contract/simple-list?customerId=${customerId}`
})
}
// 新增 CRM 合同 // 新增 CRM 合同
export const createContract = async (data: ContractVO) => { export const createContract = async (data: ContractVO) => {
return await request.post({ url: `/crm/contract/create`, data }) return await request.post({ url: `/crm/contract/create`, data })
...@@ -76,3 +102,13 @@ export const submitContract = async (id: number) => { ...@@ -76,3 +102,13 @@ export const submitContract = async (id: number) => {
export const transferContract = async (data: TransferReqVO) => { export const transferContract = async (data: TransferReqVO) => {
return await request.put({ url: '/crm/contract/transfer', data }) return await request.put({ url: '/crm/contract/transfer', data })
} }
// 获得待审核合同数量
export const getAuditContractCount = async () => {
return await request.get({ url: '/crm/contract/audit-count' })
}
// 获得即将到期(提醒)的合同数量
export const getRemindContractCount = async () => {
return await request.get({ url: '/crm/contract/remind-count' })
}
...@@ -90,6 +90,11 @@ export const importCustomerTemplate = () => { ...@@ -90,6 +90,11 @@ export const importCustomerTemplate = () => {
return request.download({ url: '/crm/customer/get-import-template' }) return request.download({ url: '/crm/customer/get-import-template' })
} }
// 导入客户
export const handleImport = async (formData) => {
return await request.upload({ url: `/crm/customer/import`, data: formData })
}
// 客户列表 // 客户列表
export const getCustomerSimpleList = async () => { export const getCustomerSimpleList = async () => {
return await request.get({ url: `/crm/customer/simple-list` }) return await request.get({ url: `/crm/customer/simple-list` })
......
...@@ -11,7 +11,17 @@ export interface FollowUpRecordVO { ...@@ -11,7 +11,17 @@ export interface FollowUpRecordVO {
fileUrls: string[] // 附件 fileUrls: string[] // 附件
nextTime: Date // 下次联系时间 nextTime: Date // 下次联系时间
businessIds: number[] // 关联的商机编号数组 businessIds: number[] // 关联的商机编号数组
businesses: {
id: number
name: string
}[] // 关联的商机数组
contactIds: number[] // 关联的联系人编号数组 contactIds: number[] // 关联的联系人编号数组
contacts: {
id: number
name: string
}[] // 关联的联系人数组
creator: string
creatorName?: string
} }
// 跟进记录 API // 跟进记录 API
...@@ -21,28 +31,13 @@ export const FollowUpRecordApi = { ...@@ -21,28 +31,13 @@ export const FollowUpRecordApi = {
return await request.get({ url: `/crm/follow-up-record/page`, params }) 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) => { createFollowUpRecord: async (data: FollowUpRecordVO) => {
return await request.post({ url: `/crm/follow-up-record/create`, data }) 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) => { deleteFollowUpRecord: async (id: number) => {
return await request.delete({ url: `/crm/follow-up-record/delete?id=` + id }) 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 })
} }
} }
...@@ -8,21 +8,21 @@ export interface ProductVO { ...@@ -8,21 +8,21 @@ export interface ProductVO {
price: number price: number
status: number status: number
categoryId: number categoryId: number
categoryName?: string
description: string description: string
ownerUserId: number ownerUserId: number
} }
export interface ProductExpandVO extends ProductVO {
count: number
discountPercent: number
totalPrice: number
}
// 查询产品列表 // 查询产品列表
export const getProductPage = async (params) => { export const getProductPage = async (params) => {
return await request.get({ url: `/crm/product/page`, params }) return await request.get({ url: `/crm/product/page`, params })
} }
// 获得产品精简列表
export const getProductSimpleList = async () => {
return await request.get({ url: `/crm/product/simple-list` })
}
// 查询产品详情 // 查询产品详情
export const getProduct = async (id: number) => { export const getProduct = async (id: number) => {
return await request.get({ url: `/crm/product/get?id=` + id }) return await request.get({ url: `/crm/product/get?id=` + id })
......
...@@ -5,15 +5,24 @@ export interface ReceivableVO { ...@@ -5,15 +5,24 @@ export interface ReceivableVO {
no: string no: string
planId: number planId: number
customerId: number customerId: number
customerName?: string
contractId: number contractId: number
contract?: {
no: string
totalPrice: number
}
auditStatus: number auditStatus: number
processInstanceId: number processInstanceId: number
returnTime: Date returnTime: Date
returnType: string returnType: string
price: number price: number
ownerUserId: number ownerUserId: number
sort: number ownerUserName?: string
remark: string remark: string
creator: string // 创建人
creatorName?: string // 创建人名称
createTime: Date // 创建时间
updateTime: Date // 更新时间
} }
// 查询回款列表 // 查询回款列表
...@@ -50,3 +59,8 @@ export const deleteReceivable = async (id: number) => { ...@@ -50,3 +59,8 @@ export const deleteReceivable = async (id: number) => {
export const exportReceivable = async (params) => { export const exportReceivable = async (params) => {
return await request.download({ url: `/crm/receivable/export-excel`, params }) return await request.download({ url: `/crm/receivable/export-excel`, params })
} }
// 提交审核
export const submitReceivable = async (id: number) => {
return await request.put({ url: `/crm/receivable/submit?id=${id}` })
}
...@@ -4,18 +4,26 @@ export interface ReceivablePlanVO { ...@@ -4,18 +4,26 @@ export interface ReceivablePlanVO {
id: number id: number
period: number period: number
receivableId: number receivableId: number
status: number
checkStatus: string
processInstanceId: number
price: number price: number
returnTime: Date returnTime: Date
remindDays: number remindDays: number
returnType: number
remindTime: Date remindTime: Date
customerId: number customerId: number
customerName?: string
contractId: number contractId: number
contractNo?: string
ownerUserId: number ownerUserId: number
sort: number ownerUserName?: string
remark: string remark: string
creator: string // 创建人
creatorName?: string // 创建人名称
createTime: Date // 创建时间
updateTime: Date // 更新时间
receivable?: {
price: number
returnTime: Date
}
} }
// 查询回款计划列表 // 查询回款计划列表
...@@ -33,6 +41,13 @@ export const getReceivablePlan = async (id: number) => { ...@@ -33,6 +41,13 @@ export const getReceivablePlan = async (id: number) => {
return await request.get({ url: `/crm/receivable-plan/get?id=` + id }) return await request.get({ url: `/crm/receivable-plan/get?id=` + id })
} }
// 查询回款计划下拉数据
export const getReceivablePlanSimpleList = async (customerId: number, contractId: number) => {
return await request.get({
url: `/crm/receivable-plan/simple-list?customerId=${customerId}&contractId=${contractId}`
})
}
// 新增回款计划 // 新增回款计划
export const createReceivablePlan = async (data: ReceivablePlanVO) => { export const createReceivablePlan = async (data: ReceivablePlanVO) => {
return await request.post({ url: `/crm/receivable-plan/create`, data }) return await request.post({ url: `/crm/receivable-plan/create`, data })
......
import request from '@/config/axios' import request from '@/config/axios'
export interface BiRankRespVO { export interface StatisticsRankRespVO {
count: number count: number
nickname: string nickname: string
deptName: string deptName: string
} }
// 排行 API // 排行 API
export const RankApi = { export const StatisticsRankApi = {
// 获得合同排行榜 // 获得合同排行榜
getContractPriceRank: (params: any) => { getContractPriceRank: (params: any) => {
return request.get({ return request.get({
url: '/crm/bi-rank/get-contract-price-rank', url: '/crm/statistics-rank/get-contract-price-rank',
params params
}) })
}, },
// 获得回款排行榜 // 获得回款排行榜
getReceivablePriceRank: (params: any) => { getReceivablePriceRank: (params: any) => {
return request.get({ return request.get({
url: '/crm/bi-rank/get-receivable-price-rank', url: '/crm/statistics-rank/get-receivable-price-rank',
params params
}) })
}, },
// 签约合同排行 // 签约合同排行
getContractCountRank: (params: any) => { getContractCountRank: (params: any) => {
return request.get({ return request.get({
url: '/crm/bi-rank/get-contract-count-rank', url: '/crm/statistics-rank/get-contract-count-rank',
params params
}) })
}, },
// 产品销量排行 // 产品销量排行
getProductSalesRank: (params: any) => { getProductSalesRank: (params: any) => {
return request.get({ return request.get({
url: '/crm/bi-rank/get-product-sales-rank', url: '/crm/statistics-rank/get-product-sales-rank',
params params
}) })
}, },
// 新增客户数排行 // 新增客户数排行
getCustomerCountRank: (params: any) => { getCustomerCountRank: (params: any) => {
return request.get({ return request.get({
url: '/crm/bi-rank/get-customer-count-rank', url: '/crm/statistics-rank/get-customer-count-rank',
params params
}) })
}, },
// 新增联系人数排行 // 新增联系人数排行
getContactsCountRank: (params: any) => { getContactsCountRank: (params: any) => {
return request.get({ return request.get({
url: '/crm/bi-rank/get-contacts-count-rank', url: '/crm/statistics-rank/get-contacts-count-rank',
params params
}) })
}, },
// 跟进次数排行 // 跟进次数排行
getFollowCountRank: (params: any) => { getFollowCountRank: (params: any) => {
return request.get({ return request.get({
url: '/crm/bi-rank/get-follow-count-rank', url: '/crm/statistics-rank/get-follow-count-rank',
params params
}) })
}, },
// 跟进客户数排行 // 跟进客户数排行
getFollowCustomerCountRank: (params: any) => { getFollowCustomerCountRank: (params: any) => {
return request.get({ return request.get({
url: '/crm/bi-rank/get-follow-customer-count-rank', url: '/crm/statistics-rank/get-follow-customer-count-rank',
params params
}) })
} }
......
...@@ -104,7 +104,6 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -104,7 +104,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
} }
] ]
}, },
{ {
path: '/dict', path: '/dict',
component: Layout, component: Layout,
...@@ -519,6 +518,17 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -519,6 +518,17 @@ const remainingRouter: AppRouteRecordRaw[] = [
component: () => import('@/views/crm/customer/detail/index.vue') component: () => import('@/views/crm/customer/detail/index.vue')
}, },
{ {
path: 'business/detail/:id',
name: 'CrmBusinessDetail',
meta: {
title: '商机详情',
noCache: true,
hidden: true,
activeMenu: '/crm/business'
},
component: () => import('@/views/crm/business/detail/index.vue')
},
{
path: 'contract/detail/:id', path: 'contract/detail/:id',
name: 'CrmContractDetail', name: 'CrmContractDetail',
meta: { meta: {
...@@ -530,6 +540,28 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -530,6 +540,28 @@ const remainingRouter: AppRouteRecordRaw[] = [
component: () => import('@/views/crm/contract/detail/index.vue') component: () => import('@/views/crm/contract/detail/index.vue')
}, },
{ {
path: 'receivable-plan/detail/:id',
name: 'CrmReceivablePlanDetail',
meta: {
title: '回款计划详情',
noCache: true,
hidden: true,
activeMenu: '/crm/receivable-plan'
},
component: () => import('@/views/crm/receivable/plan/detail/index.vue')
},
{
path: 'receivable/detail/:id',
name: 'CrmReceivableDetail',
meta: {
title: '回款详情',
noCache: true,
hidden: true,
activeMenu: '/crm/receivable'
},
component: () => import('@/views/crm/receivable/detail/index.vue')
},
{
path: 'contact/detail/:id', path: 'contact/detail/:id',
name: 'CrmContactDetail', name: 'CrmContactDetail',
meta: { meta: {
......
...@@ -234,6 +234,7 @@ const getProcessInstance = async () => { ...@@ -234,6 +234,7 @@ const getProcessInstance = async () => {
fApi.value?.fapi?.disabled(true) fApi.value?.fapi?.disabled(true)
}) })
} else { } else {
// 注意:data.processDefinition.formCustomViewPath 是组件的全路径,例如说:/crm/contract/detail/index.vue
BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath) BusinessFormComponent.value = registerComponent(data.processDefinition.formCustomViewPath)
} }
......
...@@ -30,8 +30,14 @@ ...@@ -30,8 +30,14 @@
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true"> <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" fixed="left" label="合同编号" prop="no" width="130" /> <el-table-column align="center" fixed="left" label="合同编号" prop="no" width="180" />
<el-table-column align="center" label="合同名称" prop="name" width="130" /> <el-table-column align="center" fixed="left" label="合同名称" prop="name" width="160">
<template #default="scope">
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
{{ scope.row.name }}
</el-link>
</template>
</el-table-column>
<el-table-column align="center" label="客户名称" prop="customerName" width="120"> <el-table-column align="center" label="客户名称" prop="customerName" width="120">
<template #default="scope"> <template #default="scope">
<el-link <el-link
...@@ -43,8 +49,24 @@ ...@@ -43,8 +49,24 @@
</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<!-- TODO @puhui999:做了商机详情后,可以把这个超链接加上 --> <el-table-column align="center" label="商机名称" prop="businessName" width="130">
<el-table-column align="center" label="商机名称" prop="businessName" width="130" /> <template #default="scope">
<el-link
:underline="false"
type="primary"
@click="openBusinessDetail(scope.row.businessId)"
>
{{ scope.row.businessName }}
</el-link>
</template>
</el-table-column>
<el-table-column
align="center"
label="合同金额(元)"
prop="totalPrice"
width="140"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column <el-table-column
align="center" align="center"
label="下单时间" label="下单时间"
...@@ -54,13 +76,6 @@ ...@@ -54,13 +76,6 @@
/> />
<el-table-column <el-table-column
align="center" align="center"
label="合同金额"
prop="price"
width="130"
:formatter="fenToYuanFormat"
/>
<el-table-column
align="center"
label="合同开始时间" label="合同开始时间"
prop="startTime" prop="startTime"
width="120" width="120"
...@@ -78,17 +93,41 @@ ...@@ -78,17 +93,41 @@
<el-link <el-link
:underline="false" :underline="false"
type="primary" type="primary"
@click="openContactDetail(scope.row.contactId)" @click="openContactDetail(scope.row.signContactId)"
> >
{{ scope.row.contactName }} {{ scope.row.signContactName }}
</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="公司签约人" prop="signUserName" width="130" /> <el-table-column align="center" label="公司签约人" prop="signUserName" width="130" />
<el-table-column align="center" label="备注" prop="remark" width="130" /> <el-table-column align="center" label="备注" prop="remark" width="200" />
<!-- TODO @puhui999:后续可加 【已收款金额】、【未收款金额】 --> <el-table-column
align="center"
label="已回款金额(元)"
prop="totalReceivablePrice"
width="140"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column
align="center"
label="未回款金额(元)"
prop="totalReceivablePrice"
width="140"
:formatter="erpPriceTableColumnFormatter"
>
<template #default="scope">
{{ erpPriceInputFormatter(scope.row.totalPrice - scope.row.totalReceivablePrice) }}
</template>
</el-table-column>
<el-table-column
:formatter="dateFormatter"
align="center"
label="最后跟进时间"
prop="contactLastTime"
width="180px"
/>
<el-table-column align="center" label="负责人" prop="ownerUserName" width="120" /> <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="ownerUserDeptName" width="100px" />
<el-table-column <el-table-column
:formatter="dateFormatter" :formatter="dateFormatter"
align="center" align="center"
...@@ -103,11 +142,24 @@ ...@@ -103,11 +142,24 @@
prop="createTime" prop="createTime"
width="180px" width="180px"
/> />
<el-table-column align="center" label="创建人" prop="creatorName" width="120" />
<el-table-column align="center" fixed="right" label="合同状态" prop="auditStatus" width="120"> <el-table-column align="center" fixed="right" label="合同状态" prop="auditStatus" width="120">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" /> <dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column fixed="right" label="操作" width="90">
<template #default="scope">
<el-button
link
v-hasPermi="['crm:contract:update']"
type="primary"
@click="handleProcessDetail(scope.row)"
>
查看审批
</el-button>
</template>
</el-table-column>
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
<Pagination <Pagination
...@@ -122,9 +174,9 @@ ...@@ -122,9 +174,9 @@
<script setup lang="ts" name="CheckContract"> <script setup lang="ts" name="CheckContract">
import { dateFormatter, dateFormatter2 } from '@/utils/formatTime' import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
import * as ContractApi from '@/api/crm/contract' import * as ContractApi from '@/api/crm/contract'
import { fenToYuanFormat } from '@/utils/formatter'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { AUDIT_STATUS } from './common' import { AUDIT_STATUS } from './common'
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数 const total = ref(0) // 列表的总页数
...@@ -132,7 +184,8 @@ const list = ref([]) // 列表的数据 ...@@ -132,7 +184,8 @@ const list = ref([]) // 列表的数据
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
auditStatus: 20 sceneType: 1, // 我负责的
auditStatus: 10
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
...@@ -154,8 +207,18 @@ const handleQuery = () => { ...@@ -154,8 +207,18 @@ const handleQuery = () => {
getList() getList()
} }
/** 查看审批 */
const handleProcessDetail = (row: ContractApi.ContractVO) => {
push({ name: 'BpmProcessInstanceDetail', query: { id: row.processInstanceId } })
}
/** 打开合同详情 */
const { push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'CrmContractDetail', params: { id } })
}
/** 打开客户详情 */ /** 打开客户详情 */
const { push } = useRouter() // 路由
const openCustomerDetail = (id: number) => { const openCustomerDetail = (id: number) => {
push({ name: 'CrmCustomerDetail', params: { id } }) push({ name: 'CrmCustomerDetail', params: { id } })
} }
...@@ -165,6 +228,16 @@ const openContactDetail = (id: number) => { ...@@ -165,6 +228,16 @@ const openContactDetail = (id: number) => {
push({ name: 'CrmContactDetail', params: { id } }) push({ name: 'CrmContactDetail', params: { id } })
} }
/** 打开商机详情 */
const openBusinessDetail = (id: number) => {
push({ name: 'CrmBusinessDetail', params: { id } })
}
/** 激活时 */
onActivated(async () => {
await getList()
})
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(() => {
getList() getList()
......
...@@ -30,8 +30,14 @@ ...@@ -30,8 +30,14 @@
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true"> <el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" fixed="left" label="合同编号" prop="no" width="130" /> <el-table-column align="center" fixed="left" label="合同编号" prop="no" width="180" />
<el-table-column align="center" label="合同名称" prop="name" width="130" /> <el-table-column align="center" fixed="left" label="合同名称" prop="name" width="160">
<template #default="scope">
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
{{ scope.row.name }}
</el-link>
</template>
</el-table-column>
<el-table-column align="center" label="客户名称" prop="customerName" width="120"> <el-table-column align="center" label="客户名称" prop="customerName" width="120">
<template #default="scope"> <template #default="scope">
<el-link <el-link
...@@ -43,8 +49,24 @@ ...@@ -43,8 +49,24 @@
</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<!-- TODO @puhui999:做了商机详情后,可以把这个超链接加上 --> <el-table-column align="center" label="商机名称" prop="businessName" width="130">
<el-table-column align="center" label="商机名称" prop="businessName" width="130" /> <template #default="scope">
<el-link
:underline="false"
type="primary"
@click="openBusinessDetail(scope.row.businessId)"
>
{{ scope.row.businessName }}
</el-link>
</template>
</el-table-column>
<el-table-column
align="center"
label="合同金额(元)"
prop="totalPrice"
width="140"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column <el-table-column
align="center" align="center"
label="下单时间" label="下单时间"
...@@ -54,13 +76,6 @@ ...@@ -54,13 +76,6 @@
/> />
<el-table-column <el-table-column
align="center" align="center"
label="合同金额"
prop="price"
width="130"
:formatter="fenToYuanFormat"
/>
<el-table-column
align="center"
label="合同开始时间" label="合同开始时间"
prop="startTime" prop="startTime"
width="120" width="120"
...@@ -78,17 +93,41 @@ ...@@ -78,17 +93,41 @@
<el-link <el-link
:underline="false" :underline="false"
type="primary" type="primary"
@click="openContactDetail(scope.row.contactId)" @click="openContactDetail(scope.row.signContactId)"
> >
{{ scope.row.contactName }} {{ scope.row.signContactName }}
</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="公司签约人" prop="signUserName" width="130" /> <el-table-column align="center" label="公司签约人" prop="signUserName" width="130" />
<el-table-column align="center" label="备注" prop="remark" width="130" /> <el-table-column align="center" label="备注" prop="remark" width="200" />
<!-- TODO @puhui999:后续可加 【已收款金额】、【未收款金额】 --> <el-table-column
align="center"
label="已回款金额(元)"
prop="totalReceivablePrice"
width="140"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column
align="center"
label="未回款金额(元)"
prop="totalReceivablePrice"
width="140"
:formatter="erpPriceTableColumnFormatter"
>
<template #default="scope">
{{ erpPriceInputFormatter(scope.row.totalPrice - scope.row.totalReceivablePrice) }}
</template>
</el-table-column>
<el-table-column
:formatter="dateFormatter"
align="center"
label="最后跟进时间"
prop="contactLastTime"
width="180px"
/>
<el-table-column align="center" label="负责人" prop="ownerUserName" width="120" /> <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="ownerUserDeptName" width="100px" />
<el-table-column <el-table-column
:formatter="dateFormatter" :formatter="dateFormatter"
align="center" align="center"
...@@ -103,11 +142,24 @@ ...@@ -103,11 +142,24 @@
prop="createTime" prop="createTime"
width="180px" width="180px"
/> />
<el-table-column align="center" label="创建人" prop="creatorName" width="120" />
<el-table-column align="center" fixed="right" label="合同状态" prop="auditStatus" width="120"> <el-table-column align="center" fixed="right" label="合同状态" prop="auditStatus" width="120">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" /> <dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column fixed="right" label="操作" width="90">
<template #default="scope">
<el-button
link
v-hasPermi="['crm:contract:update']"
type="primary"
@click="handleProcessDetail(scope.row)"
>
查看审批
</el-button>
</template>
</el-table-column>
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
<Pagination <Pagination
...@@ -125,8 +177,7 @@ import * as ContractApi from '@/api/crm/contract' ...@@ -125,8 +177,7 @@ import * as ContractApi from '@/api/crm/contract'
import { fenToYuanFormat } from '@/utils/formatter' import { fenToYuanFormat } from '@/utils/formatter'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { CONTRACT_EXPIRY_TYPE } from './common' import { CONTRACT_EXPIRY_TYPE } from './common'
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
const { push } = useRouter() // 路由
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数 const total = ref(0) // 列表的总页数
...@@ -134,6 +185,7 @@ const list = ref([]) // 列表的数据 ...@@ -134,6 +185,7 @@ const list = ref([]) // 列表的数据
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
sceneType: '1', // 自己负责的
expiryType: 1 expiryType: 1
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
...@@ -156,6 +208,17 @@ const handleQuery = () => { ...@@ -156,6 +208,17 @@ const handleQuery = () => {
getList() getList()
} }
/** 查看审批 */
const handleProcessDetail = (row: ContractApi.ContractVO) => {
push({ name: 'BpmProcessInstanceDetail', query: { id: row.processInstanceId } })
}
/** 打开合同详情 */
const { push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'CrmContractDetail', params: { id } })
}
/** 打开客户详情 */ /** 打开客户详情 */
const openCustomerDetail = (id: number) => { const openCustomerDetail = (id: number) => {
push({ name: 'CrmCustomerDetail', params: { id } }) push({ name: 'CrmCustomerDetail', params: { id } })
...@@ -166,10 +229,18 @@ const openContactDetail = (id: number) => { ...@@ -166,10 +229,18 @@ const openContactDetail = (id: number) => {
push({ name: 'CrmContactDetail', params: { id } }) push({ name: 'CrmContactDetail', params: { id } })
} }
/** 打开商机详情 */
const openBusinessDetail = (id: number) => {
push({ name: 'CrmBusinessDetail', params: { id } })
}
/** 激活时 */
onActivated(async () => {
await getList()
})
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(() => {
getList() getList()
}) })
</script> </script>
<style scoped></style>
...@@ -130,8 +130,8 @@ const list = ref([]) // 列表的数据 ...@@ -130,8 +130,8 @@ const list = ref([]) // 列表的数据
const queryParams = ref({ const queryParams = ref({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
followUpStatus: false, sceneType: 1,
sceneType: 1 followUpStatus: false
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
...@@ -158,10 +158,13 @@ const openDetail = (id: number) => { ...@@ -158,10 +158,13 @@ const openDetail = (id: number) => {
push({ name: 'CrmCustomerDetail', params: { id } }) push({ name: 'CrmCustomerDetail', params: { id } })
} }
/** 激活时 */
onActivated(async () => {
await getList()
})
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(() => {
getList() getList()
}) })
</script> </script>
<style scoped></style>
...@@ -29,32 +29,31 @@ ...@@ -29,32 +29,31 @@
</ContentWrap> </ContentWrap>
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true"> <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="客户名称" fixed="left" prop="name" width="160">
<el-table-column align="center" label="客户名称" prop="name" width="160">
<template #default="scope"> <template #default="scope">
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)"> <el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
{{ scope.row.name }} {{ scope.row.name }}
</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<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="source" width="100"> <el-table-column align="center" label="客户来源" prop="source" width="100">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" /> <dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="所属行业" prop="industryId" width="120"> <el-table-column label="手机" align="center" prop="mobile" width="120" />
<el-table-column label="电话" align="center" prop="telephone" width="130" />
<el-table-column label="邮箱" align="center" prop="email" width="180" />
<el-table-column align="center" label="客户级别" prop="level" width="135">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" /> <dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="客户级别" prop="level" width="120"> <el-table-column align="center" label="客户行业" prop="industryId" width="100">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" /> <dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="网址" prop="website" width="200" />
<el-table-column <el-table-column
:formatter="dateFormatter" :formatter="dateFormatter"
align="center" align="center"
...@@ -63,12 +62,16 @@ ...@@ -63,12 +62,16 @@
width="180px" width="180px"
/> />
<el-table-column align="center" label="备注" prop="remark" width="200" /> <el-table-column align="center" label="备注" prop="remark" width="200" />
<el-table-column align="center" label="锁定状态" prop="lockStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.lockStatus" />
</template>
</el-table-column>
<el-table-column align="center" label="成交状态" prop="dealStatus"> <el-table-column align="center" label="成交状态" prop="dealStatus">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" /> <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="距进入公海天数" prop="poolDay" width="100px" />
<el-table-column <el-table-column
:formatter="dateFormatter" :formatter="dateFormatter"
align="center" align="center"
...@@ -76,10 +79,17 @@ ...@@ -76,10 +79,17 @@
prop="contactLastTime" prop="contactLastTime"
width="180px" width="180px"
/> />
<el-table-column align="center" label="最后跟进记录" prop="contactLastContent" width="200" />
<el-table-column label="地址" align="center" prop="detailAddress" width="180" />
<el-table-column align="center" label="距离进入公海天数" prop="poolDay" width="140">
<template #default="scope"> {{ scope.row.poolDay }}</template>
</el-table-column>
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
<el-table-column <el-table-column
:formatter="dateFormatter" :formatter="dateFormatter"
align="center" align="center"
label="创建时间" label="更新时间"
prop="updateTime" prop="updateTime"
width="180px" width="180px"
/> />
...@@ -90,8 +100,6 @@ ...@@ -90,8 +100,6 @@
prop="createTime" prop="createTime"
width="180px" width="180px"
/> />
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" /> <el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
......
...@@ -20,8 +20,9 @@ export const CONTACT_STATUS = [ ...@@ -20,8 +20,9 @@ export const CONTACT_STATUS = [
/** 审批状态 */ /** 审批状态 */
export const AUDIT_STATUS = [ export const AUDIT_STATUS = [
{ label: '已审批', value: 20 }, { label: '待审批', value: 10 },
{ label: '待审批', value: 10 } { label: '审核通过', value: 20 },
{ label: '审核不通过', value: 30 }
] ]
/** 回款提醒类型 */ /** 回款提醒类型 */
......
...@@ -17,9 +17,9 @@ ...@@ -17,9 +17,9 @@
<el-col :span="20" :xs="24"> <el-col :span="20" :xs="24">
<CustomerTodayContactList v-if="leftMenu === 'customerTodayContact'" /> <CustomerTodayContactList v-if="leftMenu === 'customerTodayContact'" />
<ClueFollowList v-if="leftMenu === 'clueFollow'" /> <ClueFollowList v-if="leftMenu === 'clueFollow'" />
<CheckContract v-if="leftMenu === 'checkContract'" /> <ContractAuditList v-if="leftMenu === 'contractAudit'" />
<CheckReceivables v-if="leftMenu === 'checkReceivables'" /> <CheckReceivables v-if="leftMenu === 'checkReceivables'" />
<EndContract v-if="leftMenu === 'endContract'" /> <ContractRemindList v-if="leftMenu === 'contractRemind'" />
<CustomerFollowList v-if="leftMenu === 'customerFollow'" /> <CustomerFollowList v-if="leftMenu === 'customerFollow'" />
<CustomerPutPoolRemindList v-if="leftMenu === 'customerPutPoolRemind'" /> <CustomerPutPoolRemindList v-if="leftMenu === 'customerPutPoolRemind'" />
<RemindReceivables v-if="leftMenu === 'remindReceivables'" /> <RemindReceivables v-if="leftMenu === 'remindReceivables'" />
...@@ -33,25 +33,26 @@ import CustomerFollowList from './components/CustomerFollowList.vue' ...@@ -33,25 +33,26 @@ import CustomerFollowList from './components/CustomerFollowList.vue'
import CustomerTodayContactList from './components/CustomerTodayContactList.vue' import CustomerTodayContactList from './components/CustomerTodayContactList.vue'
import CustomerPutPoolRemindList from './components/CustomerPutPoolRemindList.vue' import CustomerPutPoolRemindList from './components/CustomerPutPoolRemindList.vue'
import ClueFollowList from './components/ClueFollowList.vue' import ClueFollowList from './components/ClueFollowList.vue'
import CheckContract from './tables/CheckContract.vue' import ContractAuditList from './components/ContractAuditList.vue'
import CheckReceivables from './tables/CheckReceivables.vue' import ContractRemindList from './components/ContractRemindList.vue'
import EndContract from './tables/EndContract.vue'
import RemindReceivables from './tables/RemindReceivables.vue' import RemindReceivables from './tables/RemindReceivables.vue'
import CheckReceivables from './tables/CheckReceivables.vue'
import * as CustomerApi from '@/api/crm/customer' import * as CustomerApi from '@/api/crm/customer'
import * as ClueApi from '@/api/crm/clue' import * as ClueApi from '@/api/crm/clue'
import * as ContractApi from '@/api/crm/contract'
defineOptions({ name: 'CrmBacklog' }) defineOptions({ name: 'CrmBacklog' })
const leftMenu = ref('customerTodayContact') const leftMenu = ref('customerTodayContact')
const customerTodayContactCount = ref(0)
const clueFollowCount = ref(0) const clueFollowCount = ref(0)
const customerFollowCount = ref(0) const customerFollowCount = ref(0)
const customerPutPoolRemindCount = ref(0) const customerPutPoolRemindCount = ref(0)
const checkContractCount = ref(0) const customerTodayContactCount = ref(0)
const contractAuditCount = ref(0)
const contractRemindCount = ref(0)
const checkReceivablesCount = ref(0) const checkReceivablesCount = ref(0)
const remindReceivablesCount = ref(0) const remindReceivablesCount = ref(0)
const endContractCount = ref(0)
const leftSides = ref([ const leftSides = ref([
{ {
...@@ -76,8 +77,8 @@ const leftSides = ref([ ...@@ -76,8 +77,8 @@ const leftSides = ref([
}, },
{ {
name: '待审核合同', name: '待审核合同',
menu: 'checkContract', menu: 'contractAudit',
count: checkContractCount count: contractAuditCount
}, },
{ {
name: '待审核回款', name: '待审核回款',
...@@ -91,8 +92,8 @@ const leftSides = ref([ ...@@ -91,8 +92,8 @@ const leftSides = ref([
}, },
{ {
name: '即将到期的合同', name: '即将到期的合同',
menu: 'endContract', menu: 'contractRemind',
count: endContractCount count: contractRemindCount
} }
]) ])
...@@ -110,10 +111,10 @@ const getCount = () => { ...@@ -110,10 +111,10 @@ const getCount = () => {
) )
CustomerApi.getFollowCustomerCount().then((count) => (customerFollowCount.value = count)) CustomerApi.getFollowCustomerCount().then((count) => (customerFollowCount.value = count))
ClueApi.getFollowClueCount().then((count) => (clueFollowCount.value = count)) ClueApi.getFollowClueCount().then((count) => (clueFollowCount.value = count))
BacklogApi.getCheckContractCount().then((count) => (checkContractCount.value = count)) ContractApi.getAuditContractCount().then((count) => (contractAuditCount.value = count))
ContractApi.getRemindContractCount().then((count) => (contractRemindCount.value = count))
BacklogApi.getCheckReceivablesCount().then((count) => (checkReceivablesCount.value = count)) BacklogApi.getCheckReceivablesCount().then((count) => (checkReceivablesCount.value = count))
BacklogApi.getRemindReceivablePlanCount().then((count) => (remindReceivablesCount.value = count)) BacklogApi.getRemindReceivablePlanCount().then((count) => (remindReceivablesCount.value = count))
BacklogApi.getEndContractCount().then((count) => (endContractCount.value = count))
} }
/** 激活时 */ /** 激活时 */
......
<template>
<Dialog title="变更商机状态" v-model="dialogVisible" width="400">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="80px"
v-loading="formLoading"
>
<el-form-item label="商机阶段" prop="status">
<el-select v-model="formData.status" placeholder="请选择商机阶段" class="w-1/1">
<el-option
v-for="item in statusList"
:key="item.id"
:label="item.name + '(赢单率:' + item.percent + '%)'"
:value="item.id"
/>
<el-option
v-for="item in BusinessStatusApi.DEFAULT_STATUSES"
:key="item.endStatus"
:label="item.name + '(赢单率:' + item.percent + '%)'"
:value="-item.endStatus"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as BusinessApi from '@/api/crm/business'
import * as BusinessStatusApi from '@/api/crm/business/status'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中
const formData = ref({
id: undefined,
statusId: undefined,
endStatus: undefined,
status: undefined
})
const formRules = reactive({
status: [{ required: true, message: '商机阶段不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
const statusList = ref([]) // 商机状态列表
/** 打开弹窗 */
const open = async (business: BusinessApi.BusinessVO) => {
dialogVisible.value = true
resetForm()
formData.value = {
id: business.id,
statusId: business.statusId,
endStatus: business.endStatus,
status: business.endStatus != null ? -business.endStatus : business.statusId
}
// 加载状态列表
formLoading.value = true
try {
statusList.value = await BusinessStatusApi.getBusinessStatusSimpleList(business.statusTypeId)
} finally {
formLoading.value = false
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
await BusinessApi.updateBusinessStatus({
id: formData.value.id,
statusId: formData.value.status > 0 ? formData.value.status : undefined,
endStatus: formData.value.status < 0 ? -formData.value.status : undefined
})
message.success('更新商机状态成功')
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
statusId: undefined,
endStatus: undefined,
status: undefined
}
formRef.value?.resetFields()
}
</script>
...@@ -38,7 +38,12 @@ ...@@ -38,7 +38,12 @@
</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="商机金额" align="center" prop="price" :formatter="fenToYuanFormat" /> <el-table-column
label="商机金额"
align="center"
prop="price"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column label="客户名称" align="center" prop="customerName" /> <el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column label="商机组" align="center" prop="statusTypeName" /> <el-table-column label="商机组" align="center" prop="statusTypeName" />
<el-table-column label="商机阶段" align="center" prop="statusName" /> <el-table-column label="商机阶段" align="center" prop="statusName" />
...@@ -66,8 +71,8 @@ import * as BusinessApi from '@/api/crm/business' ...@@ -66,8 +71,8 @@ import * as BusinessApi from '@/api/crm/business'
import * as ContactApi from '@/api/crm/contact' import * as ContactApi from '@/api/crm/contact'
import BusinessForm from './../BusinessForm.vue' import BusinessForm from './../BusinessForm.vue'
import { BizTypeEnum } from '@/api/crm/permission' import { BizTypeEnum } from '@/api/crm/permission'
import { fenToYuanFormat } from '@/utils/formatter'
import BusinessListModal from './BusinessListModal.vue' import BusinessListModal from './BusinessListModal.vue'
import { erpPriceTableColumnFormatter } from '@/utils'
const message = useMessage() // 消息 const message = useMessage() // 消息
...@@ -76,6 +81,7 @@ const props = defineProps<{ ...@@ -76,6 +81,7 @@ const props = defineProps<{
bizType: number // 业务类型 bizType: number // 业务类型
bizId: number // 业务编号 bizId: number // 业务编号
customerId?: number // 关联联系人与商机时,需要传入 customerId 进行筛选 customerId?: number // 关联联系人与商机时,需要传入 customerId 进行筛选
contactId?: number // 特殊:联系人编号;在【联系人】详情中,可以传递联系人编号,默认新建的商机关联到该联系人
}>() }>()
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
...@@ -125,7 +131,7 @@ const handleQuery = () => { ...@@ -125,7 +131,7 @@ const handleQuery = () => {
/** 添加操作 */ /** 添加操作 */
const formRef = ref() const formRef = ref()
const openForm = () => { const openForm = () => {
formRef.value.open('create') formRef.value.open('create', null, props.customerId, props.contactId)
} }
/** 打开联系人详情 */ /** 打开联系人详情 */
......
...@@ -48,8 +48,8 @@ ...@@ -48,8 +48,8 @@
<el-table-column <el-table-column
label="商机金额" label="商机金额"
align="center" align="center"
prop="price" prop="totalPrice"
:formatter="fenToYuanFormat" :formatter="erpPriceTableColumnFormatter"
/> />
<el-table-column label="客户名称" align="center" prop="customerName" /> <el-table-column label="客户名称" align="center" prop="customerName" />
<el-table-column label="商机组" align="center" prop="statusTypeName" /> <el-table-column label="商机组" align="center" prop="statusTypeName" />
...@@ -75,7 +75,7 @@ ...@@ -75,7 +75,7 @@
<script setup lang="ts"> <script setup lang="ts">
import * as BusinessApi from '@/api/crm/business' import * as BusinessApi from '@/api/crm/business'
import BusinessForm from '../BusinessForm.vue' import BusinessForm from '../BusinessForm.vue'
import { fenToYuanFormat } from '@/utils/formatter' import { erpPriceTableColumnFormatter } from '@/utils'
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const props = defineProps<{ const props = defineProps<{
...@@ -99,6 +99,7 @@ const queryParams = reactive({ ...@@ -99,6 +99,7 @@ const queryParams = reactive({
/** 打开弹窗 */ /** 打开弹窗 */
const open = async () => { const open = async () => {
dialogVisible.value = true dialogVisible.value = true
queryParams.customerId = props.customerId // 解决 props.customerId 没更新到 queryParams 上的问题
await getList() await getList()
} }
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
...@@ -144,10 +145,10 @@ const submitForm = async () => { ...@@ -144,10 +145,10 @@ const submitForm = async () => {
return message.error('未选择商机') return message.error('未选择商机')
} }
dialogVisible.value = false dialogVisible.value = false
emit('success', businessIds) emit('success', businessIds, businessRef.value.getSelectionRows())
} }
/** 打开联系人详情 */ /** 打开商机详情 */
const { push } = useRouter() const { push } = useRouter()
const openDetail = (id: number) => { const openDetail = (id: number) => {
push({ name: 'CrmBusinessDetail', params: { id } }) push({ name: 'CrmBusinessDetail', params: { id } })
......
<template>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
v-loading="formLoading"
label-width="0px"
:inline-message="true"
:disabled="disabled"
>
<el-table :data="formData" class="-mt-10px">
<el-table-column label="序号" type="index" align="center" width="60" />
<el-table-column label="产品名称" min-width="180">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!">
<el-select
v-model="row.productId"
clearable
filterable
@change="onChangeProduct($event, row)"
placeholder="请选择产品"
>
<el-option
v-for="item in productList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="条码" min-width="150">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productNo" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="单位" min-width="80">
<template #default="{ row }">
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="row.productUnit" />
</template>
</el-table-column>
<el-table-column label="价格(元)" min-width="120">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productPrice" :formatter="erpPriceInputFormatter" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="售价(元)" fixed="right" min-width="140">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.businessPrice`" class="mb-0px!">
<el-input-number
v-model="row.businessPrice"
controls-position="right"
:min="0.001"
:precision="2"
class="!w-100%"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="数量" prop="count" fixed="right" min-width="120">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.count`" :rules="formRules.count" class="mb-0px!">
<el-input-number
v-model="row.count"
controls-position="right"
:min="0.001"
:precision="3"
class="!w-100%"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="合计" prop="totalPrice" fixed="right" min-width="140">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.totalPrice`" class="mb-0px!">
<el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" />
</el-form-item>
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="60">
<template #default="{ $index }">
<el-button @click="handleDelete($index)" link></el-button>
</template>
</el-table-column>
</el-table>
</el-form>
<el-row justify="center" class="mt-3" v-if="!disabled">
<el-button @click="handleAdd" round>+ 添加产品</el-button>
</el-row>
</template>
<script setup lang="ts">
import * as ProductApi from '@/api/crm/product'
import { erpPriceInputFormatter, erpPriceMultiply } from '@/utils'
import { DICT_TYPE } from '@/utils/dict'
const props = defineProps<{
products: undefined
disabled: false
}>()
const formLoading = ref(false) // 表单的加载中
const formData = ref([])
const formRules = reactive({
productId: [{ required: true, message: '产品不能为空', trigger: 'blur' }],
businessPrice: [{ required: true, message: '合同价格不能为空', trigger: 'blur' }],
count: [{ required: true, message: '产品数量不能为空', trigger: 'blur' }]
})
const formRef = ref([]) // 表单 Ref
const productList = ref<ProductApi.ProductVO[]>([]) // 产品列表
/** 初始化设置产品项 */
watch(
() => props.products,
async (val) => {
formData.value = val
},
{ immediate: true }
)
/** 监听合同产品变化,计算合同产品总价 */
watch(
() => formData.value,
(val) => {
if (!val || val.length === 0) {
return
}
// 循环处理
val.forEach((item) => {
if (item.businessPrice != null && item.count != null) {
item.totalPrice = erpPriceMultiply(item.businessPrice, item.count)
} else {
item.totalPrice = undefined
}
})
},
{ deep: true }
)
/** 新增按钮操作 */
const handleAdd = () => {
const row = {
id: undefined,
productId: undefined,
productUnit: undefined, // 产品单位
productNo: undefined, // 产品条码
productPrice: undefined, // 产品价格
businessPrice: undefined,
count: 1
}
formData.value.push(row)
}
/** 删除按钮操作 */
const handleDelete = (index: number) => {
formData.value.splice(index, 1)
}
/** 处理产品变更 */
const onChangeProduct = (productId, row) => {
const product = productList.value.find((item) => item.id === productId)
if (product) {
row.productUnit = product.unit
row.productNo = product.no
row.productPrice = product.price
row.businessPrice = product.price
}
}
/** 表单校验 */
const validate = () => {
return formRef.value.validate()
}
defineExpose({ validate })
/** 初始化 */
onMounted(async () => {
productList.value = await ProductApi.getProductSimpleList()
})
</script>
<template>
<div>
<div class="flex items-start justify-between">
<div>
<el-col>
<el-row>
<span class="text-xl font-bold">{{ business.name }}</span>
</el-row>
</el-col>
</div>
<div>
<!-- 右上:按钮 -->
<slot></slot>
</div>
</div>
</div>
<ContentWrap class="mt-10px">
<el-descriptions :column="5" direction="vertical">
<el-descriptions-item label="客户名称">{{ business.customerName }}</el-descriptions-item>
<el-descriptions-item label="商机金额(元)">
{{ erpPriceInputFormatter(business.totalPrice) }}
</el-descriptions-item>
<el-descriptions-item label="商机组">{{ business.statusTypeName }}</el-descriptions-item>
<el-descriptions-item label="负责人">{{ business.ownerUserName }}</el-descriptions-item>
<el-descriptions-item label="创建时间">
{{ formatDate(business.createTime) }}
</el-descriptions-item>
</el-descriptions>
</ContentWrap>
</template>
<script lang="ts" setup>
import * as BusinessApi from '@/api/crm/business'
import { formatDate } from '@/utils/formatTime'
import { erpPriceInputFormatter } from '@/utils'
const { business } = defineProps<{ business: BusinessApi.BusinessVO }>()
</script>
<template>
<ContentWrap>
<el-collapse v-model="activeNames">
<el-collapse-item name="basicInfo">
<template #title>
<span class="text-base font-bold">基本信息</span>
</template>
<el-descriptions :column="4">
<el-descriptions-item label="商机姓名">{{ business.name }}</el-descriptions-item>
<el-descriptions-item label="客户名称">{{ business.customerName }}</el-descriptions-item>
<el-descriptions-item label="商机金额(元)">
{{ erpPriceInputFormatter(business.totalPrice) }}
</el-descriptions-item>
<el-descriptions-item label="预计成交日期">
{{ formatDate(business.dealTime) }}
</el-descriptions-item>
<el-descriptions-item label="下次联系时间">
{{ formatDate(business.contactNextTime) }}
</el-descriptions-item>
<el-descriptions-item label="商机状态组">
{{ business.statusTypeName }}
</el-descriptions-item>
<el-descriptions-item label="商机阶段">{{ business.statusName }}</el-descriptions-item>
<el-descriptions-item label="备注">{{ business.remark }}</el-descriptions-item>
</el-descriptions>
</el-collapse-item>
<el-collapse-item name="systemInfo">
<template #title>
<span class="text-base font-bold">系统信息</span>
</template>
<el-descriptions :column="4">
<el-descriptions-item label="负责人">{{ business.ownerUserName }}</el-descriptions-item>
<el-descriptions-item label="最后跟进时间">
{{ formatDate(business.contactLastTime) }}
</el-descriptions-item>
<el-descriptions-item label="">&nbsp;</el-descriptions-item>
<el-descriptions-item label="">&nbsp;</el-descriptions-item>
<el-descriptions-item label="创建人">{{ business.creatorName }}</el-descriptions-item>
<el-descriptions-item label="创建时间">
{{ formatDate(business.createTime) }}
</el-descriptions-item>
<el-descriptions-item label="更新时间">
{{ formatDate(business.updateTime) }}
</el-descriptions-item>
</el-descriptions>
</el-collapse-item>
</el-collapse>
</ContentWrap>
</template>
<script setup lang="ts">
import * as BusinessApi from '@/api/crm/business'
import { formatDate } from '@/utils/formatTime'
import { erpPriceInputFormatter } from '@/utils'
const { business } = defineProps<{
business: BusinessApi.BusinessVO
}>()
// 展示的折叠面板
const activeNames = ref(['basicInfo', 'systemInfo'])
</script>
<template>
<ContentWrap>
<el-table :data="business.products" :stripe="true" :show-overflow-tooltip="true">
<el-table-column
align="center"
label="产品名称"
fixed="left"
prop="productName"
min-width="160"
>
<template #default="scope">
{{ scope.row.productName }}
</template>
</el-table-column>
<el-table-column label="产品条码" align="center" prop="productNo" min-width="120" />
<el-table-column align="center" label="产品单位" prop="productUnit" min-width="160">
<template #default="{ row }">
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="row.productUnit" />
</template>
</el-table-column>
<el-table-column
label="产品价格(元)"
align="center"
prop="productPrice"
min-width="140"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column
label="商机价格(元)"
align="center"
prop="businessPrice"
min-width="140"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column
align="center"
label="数量"
prop="count"
min-width="100px"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column
label="合计金额(元)"
align="center"
prop="totalPrice"
min-width="140"
:formatter="erpPriceTableColumnFormatter"
/>
</el-table>
<el-row class="mt-10px" justify="end">
<el-col :span="3"> 整单折扣:{{ erpPriceInputFormatter(business.discountPercent) }}% </el-col>
<el-col :span="4">
产品总金额:{{ erpPriceInputFormatter(business.totalProductPrice) }} 元
</el-col>
</el-row>
</ContentWrap>
</template>
<script setup lang="ts">
import * as BusinessApi from '@/api/crm/business'
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
import { DICT_TYPE } from '@/utils/dict'
const { business } = defineProps<{
business: BusinessApi.BusinessVO
}>()
</script>
<template>
<BusinessDetailsHeader v-loading="loading" :business="business">
<el-button v-if="permissionListRef?.validateWrite" @click="openForm('update', business.id)">
编辑
</el-button>
<el-button
:disabled="business.endStatus"
v-if="permissionListRef?.validateWrite"
type="success"
@click="openStatusForm()"
>
变更商机状态
</el-button>
<el-button v-if="permissionListRef?.validateOwnerUser" type="primary" @click="transfer">
转移
</el-button>
</BusinessDetailsHeader>
<el-col>
<el-tabs>
<el-tab-pane label="跟进记录">
<FollowUpList :biz-id="businessId" :biz-type="BizTypeEnum.CRM_BUSINESS" />
</el-tab-pane>
<el-tab-pane label="详细资料">
<BusinessDetailsInfo :business="business" />
</el-tab-pane>
<el-tab-pane label="联系人" lazy>
<ContactList
:biz-id="business.id!"
:biz-type="BizTypeEnum.CRM_BUSINESS"
:business-id="business.id"
:customer-id="business.customerId"
/>
</el-tab-pane>
<el-tab-pane label="产品">
<BusinessProductList :business="business" />
</el-tab-pane>
<el-tab-pane label="合同" lazy>
<ContractList :biz-id="business.id!" :biz-type="BizTypeEnum.CRM_BUSINESS" />
</el-tab-pane>
<el-tab-pane label="操作日志">
<OperateLogV2 :log-list="logList" />
</el-tab-pane>
<el-tab-pane label="团队成员">
<PermissionList
ref="permissionListRef"
:biz-id="business.id!"
:biz-type="BizTypeEnum.CRM_BUSINESS"
:show-action="true"
@quit-team="close"
/>
</el-tab-pane>
</el-tabs>
</el-col>
<!-- 表单弹窗:添加/修改 -->
<BusinessForm ref="formRef" @success="getBusiness(business.id)" />
<BusinessUpdateStatusForm ref="statusFormRef" @success="getBusiness(business.id)" />
<CrmTransferForm ref="transferFormRef" @success="close" />
</template>
<script lang="ts" setup>
import { useTagsViewStore } from '@/store/modules/tagsView'
import * as ContactApi from '@/api/crm/contact'
import * as BusinessApi from '@/api/crm/business'
import BusinessDetailsHeader from './BusinessDetailsHeader.vue'
import BusinessDetailsInfo from './BusinessDetailsInfo.vue'
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' // 团队成员列表(权限)
import { BizTypeEnum } from '@/api/crm/permission'
import { OperateLogV2VO } from '@/api/system/operatelog'
import { getOperateLogPage } from '@/api/crm/operateLog'
import BusinessForm from '@/views/crm/business/BusinessForm.vue'
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
import FollowUpList from '@/views/crm/followup/index.vue'
import ContactList from '@/views/crm/contact/components/ContactList.vue'
import BusinessUpdateStatusForm from '@/views/crm/business/BusinessUpdateStatusForm.vue'
import ContractList from '@/views/crm/contract/components/ContractList.vue'
defineOptions({ name: 'CrmBusinessDetail' })
const message = useMessage()
const businessId = ref(0) // 线索编号
const loading = ref(true) // 加载中
const business = ref<ContactApi.ContactVO>({} as ContactApi.ContactVO) // 联系人详情
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref
/** 获取详情 */
const getBusiness = async (id: number) => {
loading.value = true
try {
business.value = await BusinessApi.getBusiness(id)
await getOperateLog(id)
} finally {
loading.value = false
}
}
/** 编辑 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 变更商机状态 */
const statusFormRef = ref()
const openStatusForm = () => {
statusFormRef.value.open(business.value)
}
/** 联系人转移 */
const transferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 联系人转移表单 ref
const transfer = () => {
transferFormRef.value?.open('商机转移', business.value.id, BusinessApi.transferBusiness)
}
/** 获取操作日志 */
const logList = ref<OperateLogV2VO[]>([]) // 操作日志列表
const getOperateLog = async (contactId: number) => {
if (!contactId) {
return
}
const data = await getOperateLogPage({
bizType: BizTypeEnum.CRM_BUSINESS,
bizId: contactId
})
logList.value = data.list
}
/** 关闭窗口 */
const { delView } = useTagsViewStore() // 视图操作
const { currentRoute } = useRouter() // 路由
const close = () => {
delView(unref(currentRoute))
}
/** 初始化 */
const { params } = useRoute()
onMounted(async () => {
if (!params.id) {
message.warning('参数错误,商机不能为空!')
close()
return
}
businessId.value = params.id as unknown as number
await getBusiness(businessId.value)
})
</script>
...@@ -38,10 +38,37 @@ ...@@ -38,10 +38,37 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-tabs v-model="activeName" @tab-click="handleTabClick">
<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" :stripe="true" :show-overflow-tooltip="true"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="商机名称" align="center" prop="name" /> <el-table-column align="center" label="商机名称" fixed="left" prop="name" width="160">
<el-table-column label="客户名称" align="center" prop="customerName" /> <template #default="scope">
<el-table-column label="商机金额" align="center" prop="price" /> <el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
{{ scope.row.name }}
</el-link>
</template>
</el-table-column>
<el-table-column align="center" fixed="left" label="客户名称" prop="customerName" width="120">
<template #default="scope">
<el-link
:underline="false"
type="primary"
@click="openCustomerDetail(scope.row.customerId)"
>
{{ scope.row.customerName }}
</el-link>
</template>
</el-table-column>
<el-table-column
label="商机金额(元)"
align="center"
prop="totalPrice"
width="140"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column <el-table-column
label="预计成交日期" label="预计成交日期"
align="center" align="center"
...@@ -49,9 +76,23 @@ ...@@ -49,9 +76,23 @@
:formatter="dateFormatter" :formatter="dateFormatter"
width="180px" width="180px"
/> />
<el-table-column label="备注" align="center" prop="remark" /> <el-table-column align="center" label="备注" prop="remark" width="200" />
<el-table-column label="商机状态类型" align="center" prop="statusTypeName" /> <el-table-column
<el-table-column label="商机状态" align="center" prop="statusName" /> :formatter="dateFormatter"
align="center"
label="下次联系时间"
prop="contactNextTime"
width="180px"
/>
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
<el-table-column
:formatter="dateFormatter"
align="center"
label="最后跟进时间"
prop="contactLastTime"
width="180px"
/>
<el-table-column <el-table-column
label="更新时间" label="更新时间"
align="center" align="center"
...@@ -66,9 +107,21 @@ ...@@ -66,9 +107,21 @@
:formatter="dateFormatter" :formatter="dateFormatter"
width="180px" width="180px"
/> />
<el-table-column label="负责人" align="center" prop="ownerUserId" /> <el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
<el-table-column label="创建人" align="center" prop="creator" /> <el-table-column
<el-table-column label="跟进状态" align="center" prop="followUpStatus" /> label="商机状态组"
align="center"
prop="statusTypeName"
fixed="right"
width="140"
/>
<el-table-column
label="商机阶段"
align="center"
prop="statusName"
fixed="right"
width="120"
/>
<el-table-column label="操作" align="center" fixed="right" width="130px"> <el-table-column label="操作" align="center" fixed="right" width="130px">
<template #default="scope"> <template #default="scope">
<el-button <el-button
...@@ -108,6 +161,8 @@ import { dateFormatter } from '@/utils/formatTime' ...@@ -108,6 +161,8 @@ import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import * as BusinessApi from '@/api/crm/business' import * as BusinessApi from '@/api/crm/business'
import BusinessForm from './BusinessForm.vue' import BusinessForm from './BusinessForm.vue'
import { erpPriceTableColumnFormatter } from '@/utils'
import { TabsPaneContext } from 'element-plus'
defineOptions({ name: 'CrmBusiness' }) defineOptions({ name: 'CrmBusiness' })
...@@ -120,27 +175,12 @@ const list = ref([]) // 列表的数据 ...@@ -120,27 +175,12 @@ const list = ref([]) // 列表的数据
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
name: null, sceneType: '1', // 默认和 activeName 相等
statusTypeId: null, name: null
statusId: null,
contactNextTime: [],
customerId: null,
dealTime: [],
price: null,
discountPercent: null,
productPrice: null,
remark: null,
ownerUserId: null,
createTime: [],
roUserIds: null,
rwUserIds: null,
endStatus: null,
endRemark: null,
contactLastTime: [],
followUpStatus: null
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中 const exportLoading = ref(false) // 导出的加载中
const activeName = ref('1') // 列表 tab
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
...@@ -166,6 +206,23 @@ const resetQuery = () => { ...@@ -166,6 +206,23 @@ const resetQuery = () => {
handleQuery() handleQuery()
} }
/** tab 切换 */
const handleTabClick = (tab: TabsPaneContext) => {
queryParams.sceneType = tab.paneName
handleQuery()
}
/** 打开客户详情 */
const { currentRoute, push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'CrmBusinessDetail', params: { id } })
}
/** 打开客户详情 */
const openCustomerDetail = (id: number) => {
push({ name: 'CrmCustomerDetail', params: { id } })
}
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref() const formRef = ref()
const openForm = (type: string, id?: number) => { const openForm = (type: string, id?: number) => {
......
...@@ -7,10 +7,13 @@ ...@@ -7,10 +7,13 @@
label-width="100px" label-width="100px"
v-loading="formLoading" v-loading="formLoading"
> >
<el-form-item label="状态类型名" prop="name"> <el-form-item label="状态名" prop="name">
<el-input v-model="formData.name" placeholder="请输入状态类型名" /> <el-input v-model="formData.name" placeholder="请输入状态名" />
</el-form-item> </el-form-item>
<el-form-item label="应用部门" prop="deptIds"> <el-form-item label="应用部门" prop="deptIds">
<template #label>
<Tooltip message="不选择部门时,默认全公司生效" title="应用部门" />
</template>
<el-tree <el-tree
ref="treeRef" ref="treeRef"
:data="deptList" :data="deptList"
...@@ -21,31 +24,55 @@ ...@@ -21,31 +24,55 @@
show-checkbox show-checkbox
/> />
</el-form-item> </el-form-item>
<el-form-item label="状态设置" prop="statusList"> <el-form-item label="阶段设置" prop="statuses">
<el-table border style="width: 100%" :data="formData.statusList"> <el-table
<el-table-column align="center" label="状态" width="120" prop="star"> border
style="width: 100%"
:data="formData.statuses.concat(BusinessStatusApi.DEFAULT_STATUSES)"
>
<el-table-column align="center" label="阶段" width="70">
<template #default="scope"> <template #default="scope">
<el-text>状态{{ scope.$index + 1 }}</el-text> <el-text v-if="!scope.row.defaultStatus">阶段 {{ scope.$index + 1 }}</el-text>
<el-text v-else>结束</el-text>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="状态名称" width="120" prop="name"> <el-table-column align="center" label="阶段名称" width="160" prop="name">
<template #default="{ row }"> <template #default="{ row }">
<el-input v-model="row.name" placeholder="请输入状态名称" /> <el-input v-if="!row.endStatus" v-model="row.name" placeholder="请输入状态名称" />
<el-text v-else>{{ row.name }}</el-text>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column width="120" align="center" label="赢单率" prop="percent"> <el-table-column width="140" align="center" label="赢单率(%)" prop="percent">
<template #default="{ row }"> <template #default="{ row }">
<el-input v-model="row.percent" placeholder="请输入赢单率" /> <el-input-number
v-if="!row.endStatus"
v-model="row.percent"
placeholder="请输入赢单率"
controls-position="right"
:min="0"
:max="100"
:precision="2"
class="!w-1/1"
/>
<el-text v-else>{{ row.percent }}</el-text>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center"> <el-table-column label="操作" width="110" align="center">
<template #default="scope"> <template #default="scope">
<el-button link type="primary" @click="addStatusArea(scope.$index)"> 添加 </el-button>
<el-button <el-button
v-if="!scope.row.endStatus"
link
type="primary"
@click="addStatus(scope.$index)"
>
添加
</el-button>
<el-button
v-if="!scope.row.endStatus"
link link
type="danger" type="danger"
@click="deleteStatusArea(scope.$index)" @click="deleteStatusArea(scope.$index)"
v-show="scope.$index > 0" :disabled="formData.statuses.length <= 1"
> >
删除 删除
</el-button> </el-button>
...@@ -61,7 +88,7 @@ ...@@ -61,7 +88,7 @@
</Dialog> </Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import * as BusinessStatusTypeApi from '@/api/crm/businessStatusType' import * as BusinessStatusApi from '@/api/crm/business/status'
import { defaultProps, handleTree } from '@/utils/tree' import { defaultProps, handleTree } from '@/utils/tree'
import * as DeptApi from '@/api/system/dept' import * as DeptApi from '@/api/system/dept'
...@@ -71,15 +98,15 @@ const message = useMessage() // 消息弹窗 ...@@ -71,15 +98,15 @@ const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题 const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改 const formType = ref('') // 表单的:create - 新增;update - 修改
const formData = ref({ const formData = ref({
id: 0, id: 0,
name: '', name: '',
deptIds: [], deptIds: [],
statusList: [] statuses: []
}) })
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: '状态类型名不能为空', trigger: 'blur' }] name: [{ required: true, message: '状态名不能为空', trigger: 'blur' }]
}) })
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
const deptList = ref<Tree[]>([]) // 树形结构 const deptList = ref<Tree[]>([]) // 树形结构
...@@ -96,16 +123,16 @@ const open = async (type: string, id?: number) => { ...@@ -96,16 +123,16 @@ const open = async (type: string, id?: number) => {
if (id) { if (id) {
formLoading.value = true formLoading.value = true
try { try {
formData.value = await BusinessStatusTypeApi.getBusinessStatusType(id) formData.value = await BusinessStatusApi.getBusinessStatus(id)
treeRef.value.setCheckedKeys(formData.value.deptIds) treeRef.value.setCheckedKeys(formData.value.deptIds)
if (formData.value.statusList.length == 0) { if (formData.value.statuses.length == 0) {
addStatusArea(0) addStatus()
} }
} finally { } finally {
formLoading.value = false formLoading.value = false
} }
} else { } else {
addStatusArea(0) addStatus()
} }
// 加载部门树 // 加载部门树
deptList.value = handleTree(await DeptApi.getSimpleDeptList()) deptList.value = handleTree(await DeptApi.getSimpleDeptList())
...@@ -120,13 +147,13 @@ const submitForm = async () => { ...@@ -120,13 +147,13 @@ const submitForm = async () => {
// 提交请求 // 提交请求
formLoading.value = true formLoading.value = true
try { try {
const data = formData.value as unknown as BusinessStatusTypeApi.BusinessStatusTypeVO const data = formData.value as unknown as BusinessStatusApi.BusinessStatusTypeVO
data.deptIds = treeRef.value.getCheckedKeys(false) data.deptIds = treeRef.value.getCheckedKeys(false)
if (formType.value === 'create') { if (formType.value === 'create') {
await BusinessStatusTypeApi.createBusinessStatusType(data) await BusinessStatusApi.createBusinessStatus(data)
message.success(t('common.createSuccess')) message.success(t('common.createSuccess'))
} else { } else {
await BusinessStatusTypeApi.updateBusinessStatusType(data) await BusinessStatusApi.updateBusinessStatus(data)
message.success(t('common.updateSuccess')) message.success(t('common.updateSuccess'))
} }
dialogVisible.value = false dialogVisible.value = false
...@@ -144,24 +171,24 @@ const resetForm = () => { ...@@ -144,24 +171,24 @@ const resetForm = () => {
id: 0, id: 0,
name: '', name: '',
deptIds: [], deptIds: [],
statusList: [] statuses: []
} }
treeRef.value?.setCheckedNodes([]) treeRef.value?.setCheckedNodes([])
formRef.value?.resetFields() formRef.value?.resetFields()
} }
/** 添加状态 */ /** 添加状态 */
const addStatusArea = () => { const addStatus = () => {
const data = formData.value const data = formData.value
data.statusList.push({ data.statuses.push({
name: '', name: '',
percent: '' percent: undefined
}) })
} }
/** 删除状态 */ /** 删除状态 */
const deleteStatusArea = (index: number) => { const deleteStatusArea = (index: number) => {
const data = formData.value const data = formData.value
data.statusList.splice(index, 1) data.statuses.splice(index, 1)
} }
</script> </script>
...@@ -9,25 +9,14 @@ ...@@ -9,25 +9,14 @@
label-width="68px" label-width="68px"
> >
<el-form-item> <el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button <el-button
type="primary" type="primary"
plain plain
@click="openForm('create')" @click="openForm('create')"
v-hasPermi="['crm:business-status-type:create']" v-hasPermi="['crm:business-status:create']"
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['crm:business-status-type:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
</ContentWrap> </ContentWrap>
...@@ -35,8 +24,15 @@ ...@@ -35,8 +24,15 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="状态类型名" align="center" prop="name" /> <el-table-column label="状态组名" align="center" prop="name" />
<el-table-column label="使用的部门" align="center" prop="deptNames" /> <el-table-column label="应用部门" align="center" prop="deptNames">
<template #default="scope">
<span v-if="scope.row?.deptNames?.length > 0">
{{ scope.row.deptNames.join(' ') }}
</span>
<span v-else>全公司</span>
</template>
</el-table-column>
<el-table-column label="创建人" align="center" prop="creator" /> <el-table-column label="创建人" align="center" prop="creator" />
<el-table-column <el-table-column
label="创建时间" label="创建时间"
...@@ -51,7 +47,7 @@ ...@@ -51,7 +47,7 @@
link link
type="primary" type="primary"
@click="openForm('update', scope.row.id)" @click="openForm('update', scope.row.id)"
v-hasPermi="['crm:business-status-type:update']" v-hasPermi="['crm:business-status:update']"
> >
编辑 编辑
</el-button> </el-button>
...@@ -59,7 +55,7 @@ ...@@ -59,7 +55,7 @@
link link
type="danger" type="danger"
@click="handleDelete(scope.row.id)" @click="handleDelete(scope.row.id)"
v-hasPermi="['crm:business-status-type:delete']" v-hasPermi="['crm:business-status:delete']"
> >
删除 删除
</el-button> </el-button>
...@@ -76,16 +72,17 @@ ...@@ -76,16 +72,17 @@
</ContentWrap> </ContentWrap>
<!-- 表单弹窗:添加/修改 --> <!-- 表单弹窗:添加/修改 -->
<BusinessStatusTypeForm ref="formRef" @success="getList" /> <BusinessStatusForm ref="formRef" @success="getList" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import * as BusinessStatusTypeApi from '@/api/crm/businessStatusType' import * as BusinessStatusApi from '@/api/crm/business/status'
import BusinessStatusTypeForm from './BusinessStatusTypeForm.vue' import BusinessStatusForm from './BusinessStatusForm.vue'
import { deleteBusinessStatus } from '@/api/crm/business/status'
defineOptions({ name: 'BusinessStatusType' }) defineOptions({ name: 'CrmBusinessStatus' })
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
...@@ -104,7 +101,7 @@ const exportLoading = ref(false) // 导出的加载中 ...@@ -104,7 +101,7 @@ const exportLoading = ref(false) // 导出的加载中
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {
const data = await BusinessStatusTypeApi.getBusinessStatusTypePage(queryParams) const data = await BusinessStatusApi.getBusinessStatusPage(queryParams)
list.value = data.list list.value = data.list
total.value = data.total total.value = data.total
} finally { } finally {
...@@ -130,40 +127,19 @@ const openForm = (type: string, id?: number) => { ...@@ -130,40 +127,19 @@ const openForm = (type: string, id?: number) => {
formRef.value.open(type, id) formRef.value.open(type, id)
} }
/** 选择客户操作 */
const formCustomerRef = ref()
const openCustomerForm = (id?: number) => {
formCustomerRef.value.open(id)
}
/** 删除按钮操作 */ /** 删除按钮操作 */
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
try { try {
// 删除的二次确认 // 删除的二次确认
await message.delConfirm() await message.delConfirm()
// 发起删除 // 发起删除
await BusinessStatusTypeApi.deleteBusinessStatusType(id) await BusinessStatusApi.deleteBusinessStatus(id)
message.success(t('common.delSuccess')) message.success(t('common.delSuccess'))
// 刷新列表 // 刷新列表
await getList() await getList()
} catch {} } catch {}
} }
/** 导出按钮操作 */
const handleExport = async () => {
try {
// 导出的二次确认
await message.exportConfirm()
// 发起导出
exportLoading.value = true
const data = await BusinessStatusTypeApi.exportBusinessStatusType(queryParams)
download.excel(data, '商机状态类型.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(() => {
getList() getList()
......
...@@ -33,7 +33,7 @@ ...@@ -33,7 +33,7 @@
ref="permissionListRef" ref="permissionListRef"
:biz-id="clue.id!" :biz-id="clue.id!"
:biz-type="BizTypeEnum.CRM_CLUE" :biz-type="BizTypeEnum.CRM_CLUE"
:show-action="!permissionListRef?.isPool || false" :show-action="true"
@quit-team="close" @quit-team="close"
/> />
</el-tab-pane> </el-tab-pane>
......
...@@ -5,11 +5,32 @@ ...@@ -5,11 +5,32 @@
<Icon class="mr-5px" icon="system-uicons:contacts" /> <Icon class="mr-5px" icon="system-uicons:contacts" />
创建联系人 创建联系人
</el-button> </el-button>
<el-button
@click="openBusinessModal"
v-hasPermi="['crm:contact:create-business']"
v-if="queryParams.businessId"
>
<Icon class="mr-5px" icon="ep:circle-plus" />关联
</el-button>
<el-button
@click="deleteContactBusinessList"
v-hasPermi="['crm:contact:delete-business']"
v-if="queryParams.businessId"
>
<Icon class="mr-5px" icon="ep:remove" />解除关联
</el-button>
</el-row> </el-row>
<!-- 列表 --> <!-- 列表 -->
<ContentWrap class="mt-10px"> <ContentWrap class="mt-10px">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"> <el-table
ref="contactRef"
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
>
<el-table-column type="selection" width="55" v-if="queryParams.businessId" />
<el-table-column label="姓名" fixed="left" align="center" prop="name"> <el-table-column label="姓名" fixed="left" align="center" prop="name">
<template #default="scope"> <template #default="scope">
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)"> <el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
...@@ -20,12 +41,11 @@ ...@@ -20,12 +41,11 @@
<el-table-column label="手机号" align="center" prop="mobile" /> <el-table-column label="手机号" align="center" prop="mobile" />
<el-table-column label="职位" align="center" prop="post" /> <el-table-column label="职位" align="center" prop="post" />
<el-table-column label="直属上级" align="center" prop="parentName" /> <el-table-column label="直属上级" align="center" prop="parentName" />
<el-table-column label="是否关键决策人" align="center" prop="master"> <el-table-column label="是否关键决策人" align="center" prop="master" min-width="100">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" /> <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
</template> </template>
</el-table-column> </el-table-column>
<!-- TODO 芋艿:【操作:设为首要联系人】 -->
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
<Pagination <Pagination
...@@ -38,17 +58,26 @@ ...@@ -38,17 +58,26 @@
<!-- 表单弹窗:添加 --> <!-- 表单弹窗:添加 -->
<ContactForm ref="formRef" @success="getList" /> <ContactForm ref="formRef" @success="getList" />
<!-- 关联商机选择弹框 -->
<ContactListModal
ref="contactModalRef"
:customer-id="props.customerId"
@success="createContactBusinessList"
/>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import * as ContactApi from '@/api/crm/contact' import * as ContactApi from '@/api/crm/contact'
import ContactForm from './../ContactForm.vue' import ContactForm from './../ContactForm.vue'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { BizTypeEnum } from '@/api/crm/permission' import { BizTypeEnum } from '@/api/crm/permission'
import ContactListModal from './ContactListModal.vue'
defineOptions({ name: 'CrmContactList' }) defineOptions({ name: 'CrmContactList' })
const props = defineProps<{ const props = defineProps<{
bizType: number // 业务类型 bizType: number // 业务类型
bizId: number // 业务编号 bizId: number // 业务编号
customerId: number // 特殊:客户编号;在【商机】详情中,可以传递客户编号,默认新建的联系人关联到该客户
businessId: number // 特殊:商机编号;在【商机】详情中,可以传递商机编号,默认新建的联系人关联到该商机
}>() }>()
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
...@@ -57,8 +86,10 @@ const list = ref([]) // 列表的数据 ...@@ -57,8 +86,10 @@ const list = ref([]) // 列表的数据
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
customerId: undefined as unknown // 允许 undefined + number customerId: undefined as unknown, // 允许 undefined + number
businessId: undefined as unknown // 允许 undefined + number
}) })
const message = useMessage()
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
...@@ -73,6 +104,10 @@ const getList = async () => { ...@@ -73,6 +104,10 @@ const getList = async () => {
queryParams.customerId = props.bizId queryParams.customerId = props.bizId
data = await ContactApi.getContactPageByCustomer(queryParams) data = await ContactApi.getContactPageByCustomer(queryParams)
break break
case BizTypeEnum.CRM_BUSINESS:
queryParams.businessId = props.bizId
data = await ContactApi.getContactPageByBusiness(queryParams)
break
default: default:
return return
} }
...@@ -92,7 +127,7 @@ const handleQuery = () => { ...@@ -92,7 +127,7 @@ const handleQuery = () => {
/** 添加操作 */ /** 添加操作 */
const formRef = ref() const formRef = ref()
const openForm = () => { const openForm = () => {
formRef.value.open('create') formRef.value.open('create', undefined, props.customerId, props.businessId)
} }
/** 打开联系人详情 */ /** 打开联系人详情 */
...@@ -101,6 +136,41 @@ const openDetail = (id: number) => { ...@@ -101,6 +136,41 @@ const openDetail = (id: number) => {
push({ name: 'CrmContactDetail', params: { id } }) push({ name: 'CrmContactDetail', params: { id } })
} }
/** 打开联系人与商机的关联弹窗 */
const contactModalRef = ref()
const openBusinessModal = () => {
contactModalRef.value.open()
}
const createContactBusinessList = async (contactIds: number[]) => {
const data = {
businessId: props.bizId,
contactIds: contactIds
} as ContactApi.ContactBusiness2ReqVO
contactRef.value.getSelectionRows().forEach((row: ContactApi.ContactVO) => {
data.businessIds.push(row.id)
})
await ContactApi.createContactBusinessList2(data)
// 刷新列表
message.success('关联联系人成功')
handleQuery()
}
/** 解除联系人与商机的关联 */
const contactRef = ref()
const deleteContactBusinessList = async () => {
const data = {
businessId: props.bizId,
contactIds: contactRef.value.getSelectionRows().map((row: ContactApi.ContactVO) => row.id)
} as ContactApi.ContactBusiness2ReqVO
if (data.contactIds.length === 0) {
return message.error('未选择联系人')
}
await ContactApi.deleteContactBusinessList2(data)
// 刷新列表
message.success('取关联系人成功')
handleQuery()
}
/** 监听打开的 bizId + bizType,从而加载最新的列表 */ /** 监听打开的 bizId + bizType,从而加载最新的列表 */
watch( watch(
() => [props.bizId, props.bizType], () => [props.bizId, props.bizType],
......
<template>
<Dialog title="关联联系人" v-model="dialogVisible">
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="90px"
>
<el-form-item label="联系人名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入联系人名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button type="primary" @click="openForm()" v-hasPermi="['crm:business:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap class="mt-10px">
<el-table
v-loading="loading"
ref="contactRef"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
>
<el-table-column type="selection" width="55" />
<el-table-column label="姓名" fixed="left" align="center" prop="name">
<template #default="scope">
<el-link type="primary" :underline="false" @click="openDetail(scope.row.id)">
{{ scope.row.name }}
</el-link>
</template>
</el-table-column>
<el-table-column label="手机号" align="center" prop="mobile" />
<el-table-column label="职位" align="center" prop="post" />
<el-table-column label="直属上级" align="center" prop="parentName" />
<el-table-column label="是否关键决策人" align="center" prop="master" min-width="100">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.master" />
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
<!-- 表单弹窗:添加 -->
<ContactForm ref="formRef" @success="getList" />
</Dialog>
</template>
<script setup lang="ts">
import * as ContactApi from '@/api/crm/contact'
import ContactForm from '../ContactForm.vue'
import { erpPriceTableColumnFormatter } from '@/utils'
import { DICT_TYPE } from '@/utils/dict'
const message = useMessage() // 消息弹窗
const props = defineProps<{
customerId: number
}>()
defineOptions({ name: 'ContactListModal' })
const dialogVisible = ref(false) // 弹窗的是否展示
const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据
const queryFormRef = ref() // 搜索的表单
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
name: undefined,
customerId: props.customerId
})
/** 打开弹窗 */
const open = async () => {
dialogVisible.value = true
queryParams.customerId = props.customerId // 解决 props.customerId 没更新到 queryParams 上的问题
await getList()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ContactApi.getContactPageByCustomer(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加操作 */
const formRef = ref()
const openForm = () => {
formRef.value.open('create')
}
/** 关联联系人提交 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const contactRef = ref()
const submitForm = async () => {
const contactIds = contactRef.value.getSelectionRows().map((row: ContactApi.ContactVO) => row.id)
if (contactIds.length === 0) {
return message.error('未选择联系人')
}
dialogVisible.value = false
emit('success', contactIds, contactRef.value.getSelectionRows())
}
/** 打开联系人详情 */
const { push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'CrmContactDetail', params: { id } })
}
</script>
...@@ -16,17 +16,11 @@ ...@@ -16,17 +16,11 @@
</div> </div>
<ContentWrap class="mt-10px"> <ContentWrap class="mt-10px">
<el-descriptions :column="5" direction="vertical"> <el-descriptions :column="5" direction="vertical">
<el-descriptions-item label="客户"> <el-descriptions-item label="客户名称">{{ contact.customerName }}</el-descriptions-item>
{{ contact.customerName }} <el-descriptions-item label="职务">{{ contact.post }}</el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="手机">{{ contact.mobile }}</el-descriptions-item>
<el-descriptions-item label="职务">
{{ contact.post }}
</el-descriptions-item>
<el-descriptions-item label="手机">
{{ contact.mobile }}
</el-descriptions-item>
<el-descriptions-item label="创建时间"> <el-descriptions-item label="创建时间">
{{ contact.createTime ? formatDate(contact.createTime) : '空' }} {{ formatDate(contact.createTime) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</ContentWrap> </ContentWrap>
......
...@@ -6,60 +6,49 @@ ...@@ -6,60 +6,49 @@
<span class="text-base font-bold">基本信息</span> <span class="text-base font-bold">基本信息</span>
</template> </template>
<el-descriptions :column="4"> <el-descriptions :column="4">
<el-descriptions-item label="姓名"> <el-descriptions-item label="姓名">{{ contact.name }}</el-descriptions-item>
{{ contact.name }} <el-descriptions-item label="客户名称">{{ contact.customerName }}</el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="手机">{{ contact.mobile }}</el-descriptions-item>
<el-descriptions-item label="客户"> <el-descriptions-item label="电话">{{ contact.telephone }}</el-descriptions-item>
{{ contact.customerName }} <el-descriptions-item label="邮箱">{{ contact.email }}</el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="QQ">{{ contact.qq }}</el-descriptions-item>
<el-descriptions-item label="手机"> <el-descriptions-item label="微信">{{ contact.wechat }}</el-descriptions-item>
{{ contact.mobile }} <el-descriptions-item label="地址">
</el-descriptions-item> {{ contact.areaName }} {{ contact.detailAddress }}
<el-descriptions-item label="座机"> </el-descriptions-item>
{{ contact.telephone }} <el-descriptions-item label="职务">{{ contact.post }}</el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="直属上级">{{ contact.parentName }}</el-descriptions-item>
<el-descriptions-item label="邮箱"> <el-descriptions-item label="关键决策人">
{{ contact.email }} <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="contact.master" />
</el-descriptions-item>
<el-descriptions-item label="QQ">
{{ contact.qq }}
</el-descriptions-item>
<el-descriptions-item label="微信">
{{ contact.wechat }}
</el-descriptions-item>
<el-descriptions-item label="下次联系时间">
{{ contact.nextTime ? formatDate(contact.nextTime) : '空' }}
</el-descriptions-item>
<el-descriptions-item label="所在地">
{{ contact.areaName }}
</el-descriptions-item>
<el-descriptions-item label="详细地址">
{{ contact.detailAddress }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="性别"> <el-descriptions-item label="性别">
<dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="contact.sex" /> <dict-tag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="contact.sex" />
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="备注"> <el-descriptions-item label="下次联系时间">
{{ contact.remark }} {{ formatDate(contact.contactNextTime) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="备注">{{ contact.remark }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-collapse-item> </el-collapse-item>
<el-collapse-item name="systemInfo"> <el-collapse-item name="systemInfo">
<template #title> <template #title>
<span class="text-base font-bold">系统信息</span> <span class="text-base font-bold">系统信息</span>
</template> </template>
<el-descriptions :column="2"> <el-descriptions :column="4">
<el-descriptions-item label="负责人"> <el-descriptions-item label="负责人">{{ contact.ownerUserName }}</el-descriptions-item>
{{ contact.ownerUserName }} <el-descriptions-item label="最后跟进记录">
{{ contact.contactLastContent }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="创建人"> <el-descriptions-item label="最后跟进时间">
{{ contact.creatorName }} {{ formatDate(contact.contactLastTime) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="">&nbsp;</el-descriptions-item>
<el-descriptions-item label="创建人">{{ contact.creatorName }}</el-descriptions-item>
<el-descriptions-item label="创建时间"> <el-descriptions-item label="创建时间">
{{ contact.createTime ? formatDate(contact.createTime) : '空' }} {{ formatDate(contact.createTime) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="更新时间"> <el-descriptions-item label="更新时间">
{{ contact.updateTime ? formatDate(contact.updateTime) : '空' }} {{ formatDate(contact.updateTime) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-collapse-item> </el-collapse-item>
......
...@@ -9,6 +9,9 @@ ...@@ -9,6 +9,9 @@
</ContactDetailsHeader> </ContactDetailsHeader>
<el-col> <el-col>
<el-tabs> <el-tabs>
<el-tab-pane label="跟进记录">
<FollowUpList :biz-id="contactId" :biz-type="BizTypeEnum.CRM_CONTACT" />
</el-tab-pane>
<el-tab-pane label="详细资料"> <el-tab-pane label="详细资料">
<ContactDetailsInfo :contact="contact" /> <ContactDetailsInfo :contact="contact" />
</el-tab-pane> </el-tab-pane>
...@@ -20,7 +23,7 @@ ...@@ -20,7 +23,7 @@
ref="permissionListRef" ref="permissionListRef"
:biz-id="contact.id!" :biz-id="contact.id!"
:biz-type="BizTypeEnum.CRM_CONTACT" :biz-type="BizTypeEnum.CRM_CONTACT"
:show-action="!permissionListRef?.isPool || false" :show-action="true"
@quit-team="close" @quit-team="close"
/> />
</el-tab-pane> </el-tab-pane>
...@@ -29,13 +32,14 @@ ...@@ -29,13 +32,14 @@
:biz-id="contact.id!" :biz-id="contact.id!"
:biz-type="BizTypeEnum.CRM_CONTACT" :biz-type="BizTypeEnum.CRM_CONTACT"
:customer-id="contact.customerId" :customer-id="contact.customerId"
:contact-id="contact.id"
/> />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</el-col> </el-col>
<!-- 表单弹窗:添加/修改 --> <!-- 表单弹窗:添加/修改 -->
<ContactForm ref="formRef" @success="getContactData(contact.id)" /> <ContactForm ref="formRef" @success="getContact(contact.id)" />
<CrmTransferForm ref="crmTransferFormRef" @success="close" /> <CrmTransferForm ref="transferFormRef" @success="close" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
...@@ -49,18 +53,19 @@ import { OperateLogV2VO } from '@/api/system/operatelog' ...@@ -49,18 +53,19 @@ import { OperateLogV2VO } from '@/api/system/operatelog'
import { getOperateLogPage } from '@/api/crm/operateLog' import { getOperateLogPage } from '@/api/crm/operateLog'
import ContactForm from '@/views/crm/contact/ContactForm.vue' import ContactForm from '@/views/crm/contact/ContactForm.vue'
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue' import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
import FollowUpList from '@/views/crm/followup/index.vue'
defineOptions({ name: 'CrmContactDetail' }) defineOptions({ name: 'CrmContactDetail' })
const route = useRoute()
const message = useMessage() const message = useMessage()
const id = Number(route.params.id) // 联系人编号
const contactId = ref(0) // 线索编号
const loading = ref(true) // 加载中 const loading = ref(true) // 加载中
const contact = ref<ContactApi.ContactVO>({} as ContactApi.ContactVO) // 联系人详情 const contact = ref<ContactApi.ContactVO>({} as ContactApi.ContactVO) // 联系人详情
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref
/** 获取详情 */ /** 获取详情 */
const getContactData = async (id: number) => { const getContact = async (id: number) => {
loading.value = true loading.value = true
try { try {
contact.value = await ContactApi.getContact(id) contact.value = await ContactApi.getContact(id)
...@@ -77,9 +82,9 @@ const openForm = (type: string, id?: number) => { ...@@ -77,9 +82,9 @@ const openForm = (type: string, id?: number) => {
} }
/** 联系人转移 */ /** 联系人转移 */
const crmTransferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 联系人转移表单 ref const transferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 联系人转移表单 ref
const transfer = () => { const transfer = () => {
crmTransferFormRef.value?.open('联系人转移', contact.value.id, ContactApi.transferContact) transferFormRef.value?.open('联系人转移', contact.value.id, ContactApi.transferContact)
} }
/** 获取操作日志 */ /** 获取操作日志 */
...@@ -96,19 +101,21 @@ const getOperateLog = async (contactId: number) => { ...@@ -96,19 +101,21 @@ const getOperateLog = async (contactId: number) => {
} }
/** 关闭窗口 */ /** 关闭窗口 */
const { delView } = useTagsViewStore() // 视图操作
const { currentRoute } = useRouter() // 路由
const close = () => { const close = () => {
delView(unref(currentRoute)) delView(unref(currentRoute))
} }
/** 初始化 */ /** 初始化 */
const { delView } = useTagsViewStore() // 视图操作 const { params } = useRoute()
const { currentRoute } = useRouter() // 路由
onMounted(async () => { onMounted(async () => {
if (!id) { if (!params.id) {
message.warning('参数错误,联系人不能为空!') message.warning('参数错误,联系人不能为空!')
close() close()
return return
} }
await getContactData(id) contactId.value = params.id as unknown as number
await getContact(contactId.value)
}) })
</script> </script>
...@@ -53,15 +53,6 @@ ...@@ -53,15 +53,6 @@
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
</el-form-item> </el-form-item>
<el-form-item label="QQ" prop="qq">
<el-input
v-model="queryParams.qq"
class="!w-240px"
clearable
placeholder="请输入QQ"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="微信" prop="wechat"> <el-form-item label="微信" prop="wechat">
<el-input <el-input
v-model="queryParams.wechat" v-model="queryParams.wechat"
...@@ -109,6 +100,11 @@ ...@@ -109,6 +100,11 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-tabs v-model="activeName" @tab-click="handleTabClick">
<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 v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" fixed="left" label="联系人姓名" prop="name" width="160"> <el-table-column align="center" fixed="left" label="联系人姓名" prop="name" width="160">
<template #default="scope"> <template #default="scope">
...@@ -224,6 +220,7 @@ import * as ContactApi from '@/api/crm/contact' ...@@ -224,6 +220,7 @@ import * as ContactApi from '@/api/crm/contact'
import ContactForm from './ContactForm.vue' import ContactForm from './ContactForm.vue'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import * as CustomerApi from '@/api/crm/customer' import * as CustomerApi from '@/api/crm/customer'
import { TabsPaneContext } from 'element-plus'
defineOptions({ name: 'CrmContact' }) defineOptions({ name: 'CrmContact' })
...@@ -233,20 +230,21 @@ const { t } = useI18n() // 国际化 ...@@ -233,20 +230,21 @@ const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中 const loading = ref(true) // 列表的加载中
const total = ref(0) // 列表的总页数 const total = ref(0) // 列表的总页数
const list = ref([]) // 列表的数据 const list = ref([]) // 列表的数据
const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
sceneType: '1', // 默认和 activeName 相等
mobile: undefined, mobile: undefined,
telephone: undefined, telephone: undefined,
email: undefined, email: undefined,
customerId: undefined, customerId: undefined,
name: undefined, name: undefined,
qq: undefined,
wechat: undefined wechat: undefined
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中 const exportLoading = ref(false) // 导出的加载中
const activeName = ref('1') // 列表 tab
const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
...@@ -272,6 +270,12 @@ const resetQuery = () => { ...@@ -272,6 +270,12 @@ const resetQuery = () => {
handleQuery() handleQuery()
} }
/** tab 切换 */
const handleTabClick = (tab: TabsPaneContext) => {
queryParams.sceneType = tab.paneName
handleQuery()
}
/** 添加/修改操作 */ /** 添加/修改操作 */
const formRef = ref() const formRef = ref()
const openForm = (type: string, id?: number) => { const openForm = (type: string, id?: number) => {
......
...@@ -22,8 +22,8 @@ ...@@ -22,8 +22,8 @@
<el-table-column <el-table-column
label="合同金额(元)" label="合同金额(元)"
align="center" align="center"
prop="price" prop="totalPrice"
:formatter="fenToYuanFormat" :formatter="erpPriceTableColumnFormatter"
/> />
<el-table-column <el-table-column
label="开始时间" label="开始时间"
...@@ -61,9 +61,9 @@ ...@@ -61,9 +61,9 @@
import * as ContractApi from '@/api/crm/contract' import * as ContractApi from '@/api/crm/contract'
import ContractForm from './../ContractForm.vue' import ContractForm from './../ContractForm.vue'
import { BizTypeEnum } from '@/api/crm/permission' import { BizTypeEnum } from '@/api/crm/permission'
import { fenToYuanFormat } from '@/utils/formatter'
import { dateFormatter } from '@/utils/formatTime' import { dateFormatter } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { erpPriceTableColumnFormatter } from '@/utils'
defineOptions({ name: 'CrmContractList' }) defineOptions({ name: 'CrmContractList' })
const props = defineProps<{ const props = defineProps<{
...@@ -93,6 +93,10 @@ const getList = async () => { ...@@ -93,6 +93,10 @@ const getList = async () => {
queryParams.customerId = props.bizId queryParams.customerId = props.bizId
data = await ContractApi.getContractPageByCustomer(queryParams) data = await ContractApi.getContractPageByCustomer(queryParams)
break break
case BizTypeEnum.CRM_BUSINESS:
queryParams.businessId = props.bizId
data = await ContractApi.getContractPageByBusiness(queryParams)
break
default: default:
return return
} }
......
<template>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
v-loading="formLoading"
label-width="0px"
:inline-message="true"
:disabled="disabled"
>
<el-table :data="formData" class="-mt-10px">
<el-table-column label="序号" type="index" align="center" width="60" />
<el-table-column label="产品名称" min-width="180">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!">
<el-select
v-model="row.productId"
clearable
filterable
@change="onChangeProduct($event, row)"
placeholder="请选择产品"
>
<el-option
v-for="item in productList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="条码" min-width="150">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productNo" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="单位" min-width="80">
<template #default="{ row }">
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="row.productUnit" />
</template>
</el-table-column>
<el-table-column label="价格(元)" min-width="120">
<template #default="{ row }">
<el-form-item class="mb-0px!">
<el-input disabled v-model="row.productPrice" :formatter="erpPriceInputFormatter" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="售价(元)" fixed="right" min-width="140">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.contractPrice`" class="mb-0px!">
<el-input-number
v-model="row.contractPrice"
controls-position="right"
:min="0.001"
:precision="2"
class="!w-100%"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="数量" prop="count" fixed="right" min-width="120">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.count`" :rules="formRules.count" class="mb-0px!">
<el-input-number
v-model="row.count"
controls-position="right"
:min="0.001"
:precision="3"
class="!w-100%"
/>
</el-form-item>
</template>
</el-table-column>
<el-table-column label="合计" prop="totalPrice" fixed="right" min-width="140">
<template #default="{ row, $index }">
<el-form-item :prop="`${$index}.totalPrice`" class="mb-0px!">
<el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" />
</el-form-item>
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="60">
<template #default="{ $index }">
<el-button @click="handleDelete($index)" link></el-button>
</template>
</el-table-column>
</el-table>
</el-form>
<el-row justify="center" class="mt-3" v-if="!disabled">
<el-button @click="handleAdd" round>+ 添加产品</el-button>
</el-row>
</template>
<script setup lang="ts">
import * as ProductApi from '@/api/crm/product'
import { erpPriceInputFormatter, erpPriceMultiply } from '@/utils'
import { DICT_TYPE } from '@/utils/dict'
const props = defineProps<{
products: undefined
disabled: false
}>()
const formLoading = ref(false) // 表单的加载中
const formData = ref([])
const formRules = reactive({
productId: [{ required: true, message: '产品不能为空', trigger: 'blur' }],
contractPrice: [{ required: true, message: '合同价格不能为空', trigger: 'blur' }],
count: [{ required: true, message: '产品数量不能为空', trigger: 'blur' }]
})
const formRef = ref([]) // 表单 Ref
const productList = ref<ProductApi.ProductVO[]>([]) // 产品列表
/** 初始化设置产品项 */
watch(
() => props.products,
async (val) => {
formData.value = val
},
{ immediate: true }
)
/** 监听合同产品变化,计算合同产品总价 */
watch(
() => formData.value,
(val) => {
if (!val || val.length === 0) {
return
}
// 循环处理
val.forEach((item) => {
if (item.contractPrice != null && item.count != null) {
item.totalPrice = erpPriceMultiply(item.contractPrice, item.count)
} else {
item.totalPrice = undefined
}
})
},
{ deep: true }
)
/** 新增按钮操作 */
const handleAdd = () => {
const row = {
id: undefined,
productId: undefined,
productUnit: undefined, // 产品单位
productNo: undefined, // 产品条码
productPrice: undefined, // 产品价格
contractPrice: undefined,
count: 1
}
formData.value.push(row)
}
/** 删除按钮操作 */
const handleDelete = (index: number) => {
formData.value.splice(index, 1)
}
/** 处理产品变更 */
const onChangeProduct = (productId, row) => {
const product = productList.value.find((item) => item.id === productId)
if (product) {
row.productUnit = product.unit
row.productNo = product.no
row.productPrice = product.price
row.contractPrice = product.price
}
}
/** 表单校验 */
const validate = () => {
return formRef.value.validate()
}
defineExpose({ validate })
/** 初始化 */
onMounted(async () => {
productList.value = await ProductApi.getProductSimpleList()
})
</script>
<!-- 合同 Form 表单下的 Product 列表 -->
<template>
<el-row justify="end">
<el-button plain type="primary" @click="openForm">添加产品</el-button>
</el-row>
<el-table :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" label="产品名称" prop="name" width="120" />
<el-table-column
:formatter="fenToYuanFormat"
align="center"
label="价格"
prop="price"
width="100"
/>
<el-table-column align="center" label="产品类型" prop="categoryName" width="100" />
<el-table-column align="center" label="产品单位" prop="unit">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="scope.row.unit" />
</template>
</el-table-column>
<el-table-column align="center" label="产品编码" prop="no" />
<el-table-column align="center" fixed="right" label="数量" prop="count" width="100">
<template #default="{ row }: { row: ProductApi.ProductExpandVO }">
<el-input-number
v-model="row.count"
controls-position="right"
:min="0"
:precision="0"
class="!w-100%"
/>
</template>
</el-table-column>
<el-table-column
align="center"
fixed="right"
label="折扣(%)"
prop="discountPercent"
width="120"
>
<template #default="{ row }: { row: ProductApi.ProductExpandVO }">
<el-input-number
v-model="row.discountPercent"
controls-position="right"
:min="0"
:max="100"
:precision="0"
class="!w-100%"
/>
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="合计" prop="totalPrice" width="100">
<template #default="{ row }: { row: ProductApi.ProductExpandVO }">
{{ fenToYuan(getTotalPrice(row)) }}
</template>
</el-table-column>
<el-table-column align="center" fixed="right" label="操作" width="60">
<template #default="scope">
<el-button link type="danger" @click="handleDelete(scope.row.id)"> 移除</el-button>
</template>
</el-table-column>
</el-table>
<!-- table 选择表单 -->
<TableSelectForm ref="tableSelectFormRef" v-model="multipleSelection" title="选择产品">
<el-table-column align="center" label="产品名称" prop="name" width="160" />
<el-table-column align="center" label="产品类型" prop="categoryName" width="160" />
<el-table-column align="center" label="产品单位" prop="unit">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="scope.row.unit" />
</template>
</el-table-column>
<el-table-column align="center" label="产品编码" prop="no" />
<el-table-column
:formatter="fenToYuanFormat"
align="center"
label="价格(元)"
prop="price"
width="100"
/>
</TableSelectForm>
</template>
<script lang="ts" setup>
import * as ProductApi from '@/api/crm/product'
import { DICT_TYPE } from '@/utils/dict'
import { fenToYuanFormat } from '@/utils/formatter'
import { TableSelectForm } from '@/components/Table/index'
import { fenToYuan, floatToFixed2, yuanToFen } from '@/utils'
defineOptions({ name: 'ProductList' })
const props = withDefaults(defineProps<{ modelValue: ProductApi.ProductExpandVO[] }>(), {
modelValue: () => []
})
const emits = defineEmits<{
(e: 'update:modelValue', v: any[]): void
}>()
const list = ref<ProductApi.ProductExpandVO[]>([]) // 已添加的产品列表
const multipleSelection = ref<ProductApi.ProductExpandVO[]>([]) // 多选
/** 处理删除 */
const handleDelete = (id: number) => {
const index = list.value.findIndex((item) => item.id === id)
if (index !== -1) {
list.value.splice(index, 1)
}
}
/** 打开 Product 弹窗 */
const tableSelectFormRef = ref<InstanceType<typeof TableSelectForm>>()
const openForm = () => {
tableSelectFormRef.value?.open(ProductApi.getProductPage)
}
/** 计算 totalPrice */
const getTotalPrice = computed(() => (row: ProductApi.ProductExpandVO) => {
const totalPrice =
(Number(row.price) / 100) * Number(row.count) * (1 - Number(row.discountPercent) / 100)
row.totalPrice = isNaN(totalPrice) ? 0 : yuanToFen(totalPrice)
return isNaN(totalPrice) ? 0 : totalPrice.toFixed(2)
})
/** 编辑时合同产品回显 */
const isSetListValue = ref(false) // 判断是否已经给 list 赋值过,用于编辑表单产品回显
watch(
() => props.modelValue,
(val) => {
if (!val || val.length === 0 || isSetListValue.value) {
return
}
list.value = [
...props.modelValue.map((item) => {
item.totalPrice = floatToFixed2(item.totalPrice) as unknown as number
return item
})
]
isSetListValue.value = true
},
{ immediate: true, deep: true }
)
/** 监听列表变化,动态更新合同产品列表 */
watch(
list,
(val) => {
if (!val || val.length === 0) {
return
}
emits('update:modelValue', list.value)
},
{ deep: true }
)
// 监听产品选择结果动态添加产品到列表中,如果产品存在则不放入列表中
watch(
multipleSelection,
(val) => {
if (!val || val.length === 0) {
return
}
// 过滤出不在列表中的产品
const ids = list.value.map((item) => item.id)
const productList = multipleSelection.value.filter((item) => ids.indexOf(item.id) === -1)
if (!productList || productList.length === 0) {
return
}
list.value.push(...productList)
},
{ deep: true }
)
</script>
<template>
<ContentWrap>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="160px"
v-loading="formLoading"
>
<el-card shadow="never">
<!-- 操作 -->
<template #header>
<div class="flex items-center justify-between">
<CardTitle title="合同配置设置" />
<el-button type="primary" @click="onSubmit" v-hasPermi="['crm:contract-config:update']">
保存
</el-button>
</div>
</template>
<!-- 表单 -->
<el-form-item label="提前提醒设置" prop="notifyEnabled">
<el-radio-group
v-model="formData.notifyEnabled"
@change="changeNotifyEnable"
class="ml-4"
>
<el-radio :label="false" size="large">不提醒</el-radio>
<el-radio :label="true" size="large">提醒</el-radio>
</el-radio-group>
</el-form-item>
<div v-if="formData.notifyEnabled">
<el-form-item>
提前 <el-input-number class="mx-2" v-model="formData.notifyDays" /> 天提醒
</el-form-item>
</div>
</el-card>
</el-form>
</ContentWrap>
</template>
<script setup lang="ts">
import * as ContractConfigApi from '@/api/crm/contract/config'
import { CardTitle } from '@/components/Card'
defineOptions({ name: 'CrmContractConfig' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const formLoading = ref(false)
const formData = ref({
notifyEnabled: false,
notifyDays: undefined
})
const formRules = reactive({})
const formRef = ref() // 表单 Ref
/** 获取配置 */
const getConfig = async () => {
try {
formLoading.value = true
const data = await ContractConfigApi.getContractConfig()
if (data === null) {
return
}
formData.value = data
} finally {
formLoading.value = false
}
}
/** 提交配置 */
const onSubmit = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value as ContractConfigApi.ContractConfigVO
await ContractConfigApi.saveContractConfig(data)
message.success(t('common.updateSuccess'))
await getConfig()
formLoading.value = false
} finally {
formLoading.value = false
}
}
/** 更改提前提醒设置 */
const changeNotifyEnable = () => {
if (!formData.value.notifyEnabled) {
formData.value.notifyDays = undefined
}
}
onMounted(() => {
getConfig()
})
</script>
...@@ -21,13 +21,13 @@ ...@@ -21,13 +21,13 @@
{{ contract.customerName }} {{ contract.customerName }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="合同金额(元)"> <el-descriptions-item label="合同金额(元)">
{{ floatToFixed2(contract.price) }} {{ erpPriceInputFormatter(contract.totalPrice) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="下单时间"> <el-descriptions-item label="下单时间">
{{ contract.orderDate ? formatDate(contract.orderDate) : '空' }} {{ formatDate(contract.orderDate) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="回款金额(元)"> <el-descriptions-item label="回款金额(元)">
{{ floatToFixed2(contract.price) }} {{ erpPriceInputFormatter(contract.totalReceivablePrice) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="负责人"> <el-descriptions-item label="负责人">
{{ contract.ownerUserName }} {{ contract.ownerUserName }}
...@@ -38,7 +38,7 @@ ...@@ -38,7 +38,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import * as ContractApi from '@/api/crm/contract' import * as ContractApi from '@/api/crm/contract'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
import { floatToFixed2 } from '@/utils' import { erpPriceInputFormatter } from '@/utils'
defineOptions({ name: 'ContractDetailsHeader' }) defineOptions({ name: 'ContractDetailsHeader' })
defineProps<{ contract: ContractApi.ContractVO }>() defineProps<{ contract: ContractApi.ContractVO }>()
......
...@@ -6,33 +6,25 @@ ...@@ -6,33 +6,25 @@
<template #title> <template #title>
<span class="text-base font-bold">基本信息</span> <span class="text-base font-bold">基本信息</span>
</template> </template>
<el-descriptions :column="3"> <el-descriptions :column="4">
<el-descriptions-item label="合同编号"> <el-descriptions-item label="合同编号">{{ contract.no }}</el-descriptions-item>
{{ contract.no }} <el-descriptions-item label="合同名称">{{ contract.name }}</el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="客户名称">{{ contract.customerName }}</el-descriptions-item>
<el-descriptions-item label="合同名称"> <el-descriptions-item label="商机名称">{{ contract.businessName }}</el-descriptions-item>
{{ contract.name }}
</el-descriptions-item>
<el-descriptions-item label="客户名称">
{{ contract.customerName }}
</el-descriptions-item>
<el-descriptions-item label="商机名称">
{{ contract.businessName }}
</el-descriptions-item>
<el-descriptions-item label="合同金额(元)"> <el-descriptions-item label="合同金额(元)">
{{ contract.price }} {{ erpPriceInputFormatter(contract.totalPrice) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="下单时间"> <el-descriptions-item label="下单时间">
{{ formatDate(contract.orderDate) }} {{ formatDate(contract.orderDate) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="开始时间"> <el-descriptions-item label="合同开始时间">
{{ formatDate(contract.startTime) }} {{ formatDate(contract.startTime) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="结束时间"> <el-descriptions-item label="合同结束时间">
{{ formatDate(contract.endTime) }} {{ formatDate(contract.endTime) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="客户签约人"> <el-descriptions-item label="客户签约人">
{{ contract.contactName }} {{ contract.signContactName }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="公司签约人"> <el-descriptions-item label="公司签约人">
{{ contract.signUserName }} {{ contract.signUserName }}
...@@ -41,7 +33,7 @@ ...@@ -41,7 +33,7 @@
{{ contract.remark }} {{ contract.remark }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="合同状态"> <el-descriptions-item label="合同状态">
{{ contract.auditStatus }} <dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="contract.auditStatus" />
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-collapse-item> </el-collapse-item>
...@@ -49,18 +41,19 @@ ...@@ -49,18 +41,19 @@
<template #title> <template #title>
<span class="text-base font-bold">系统信息</span> <span class="text-base font-bold">系统信息</span>
</template> </template>
<el-descriptions :column="2"> <el-descriptions :column="4">
<el-descriptions-item label="负责人"> <el-descriptions-item label="负责人">{{ contract.ownerUserName }}</el-descriptions-item>
{{ contract.ownerUserName }} <el-descriptions-item label="最后跟进时间">
</el-descriptions-item> {{ formatDate(contract.contactLastTime) }}
<el-descriptions-item label="创建人"> </el-descriptions-item>
{{ contract.creatorName }} <el-descriptions-item label="">&nbsp;</el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="">&nbsp;</el-descriptions-item>
<el-descriptions-item label="创建人">{{ contract.creatorName }}</el-descriptions-item>
<el-descriptions-item label="创建时间"> <el-descriptions-item label="创建时间">
{{ contract.createTime ? formatDate(contract.createTime) : '空' }} {{ formatDate(contract.createTime) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="更新时间"> <el-descriptions-item label="更新时间">
{{ contract.updateTime ? formatDate(contract.updateTime) : '空' }} {{ formatDate(contract.updateTime) }}
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</el-collapse-item> </el-collapse-item>
...@@ -70,6 +63,8 @@ ...@@ -70,6 +63,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import * as ContractApi from '@/api/crm/contract' import * as ContractApi from '@/api/crm/contract'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import { erpPriceInputFormatter } from '@/utils'
defineOptions({ name: 'ContractDetailsInfo' }) defineOptions({ name: 'ContractDetailsInfo' })
defineProps<{ defineProps<{
......
<!-- 合同详情:产品列表 -->
<template> <template>
<el-table :data="list" :show-overflow-tooltip="true" :stripe="true"> <ContentWrap>
<el-table-column align="center" label="产品名称" prop="name" width="160" /> <el-table :data="contract.products" :stripe="true" :show-overflow-tooltip="true">
<el-table-column align="center" label="产品类型" prop="categoryName" width="160" /> <el-table-column
<el-table-column align="center" label="产品单位" prop="unit"> align="center"
<template #default="scope"> label="产品名称"
<dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="scope.row.unit" /> fixed="left"
</template> prop="productName"
</el-table-column> min-width="160"
<el-table-column align="center" label="产品编码" prop="no" /> >
<el-table-column <template #default="scope">
:formatter="fenToYuanFormat" {{ scope.row.productName }}
align="center" </template>
label="价格(元)" </el-table-column>
prop="price" <el-table-column label="产品条码" align="center" prop="productNo" min-width="120" />
width="100" <el-table-column align="center" label="产品单位" prop="productUnit" min-width="160">
/> <template #default="{ row }">
<el-table-column align="center" label="数量" prop="count" width="200" /> <dict-tag :type="DICT_TYPE.CRM_PRODUCT_UNIT" :value="row.productUnit" />
<el-table-column align="center" label="折扣(%)" prop="discountPercent" width="200" /> </template>
<el-table-column align="center" label="合计" prop="totalPrice" width="100"> </el-table-column>
<template #default="{ row }: { row: ProductApi.ProductExpandVO }"> <el-table-column
{{ getTotalPrice(row) }} label="产品价格(元)"
</template> align="center"
</el-table-column> prop="productPrice"
</el-table> min-width="140"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column
label="合同价格(元)"
align="center"
prop="contractPrice"
min-width="140"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column
align="center"
label="数量"
prop="count"
min-width="100px"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column
label="合计金额(元)"
align="center"
prop="totalPrice"
min-width="140"
:formatter="erpPriceTableColumnFormatter"
/>
</el-table>
<el-row class="mt-10px" justify="end">
<el-col :span="3"> 整单折扣:{{ erpPriceInputFormatter(contract.discountPercent) }}% </el-col>
<el-col :span="4">
产品总金额:{{ erpPriceInputFormatter(contract.totalProductPrice) }} 元
</el-col>
</el-row>
</ContentWrap>
</template> </template>
<script setup lang="ts">
<script lang="ts" setup> import * as ContractApi from '@/api/crm/contract'
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { fenToYuanFormat } from '@/utils/formatter'
import * as ProductApi from '@/api/crm/product'
import { floatToFixed2, yuanToFen } from '@/utils'
defineOptions({ name: 'ContractProductList' })
const props = withDefaults(defineProps<{ modelValue: ProductApi.ProductExpandVO[] }>(), {
modelValue: () => []
})
const list = ref<ProductApi.ProductExpandVO[]>([]) // 产品列表
/** 计算 totalPrice */
const getTotalPrice = computed(() => (row: ProductApi.ProductExpandVO) => {
const totalPrice =
(Number(row.price) / 100) * Number(row.count) * (1 - Number(row.discountPercent) / 100)
row.totalPrice = isNaN(totalPrice) ? 0 : yuanToFen(totalPrice)
return isNaN(totalPrice) ? 0 : totalPrice.toFixed(2)
})
/** 编辑时合同产品回显 */ const { contract } = defineProps<{
const isSetListValue = ref(false) // 判断是否已经给 list 赋值过,用于编辑表单产品回显 contract: ContractApi.ContractVO
watch( }>()
() => props.modelValue,
(val) => {
if (!val || val.length === 0 || isSetListValue.value) {
return
}
list.value = [
...props.modelValue.map((item) => {
item.totalPrice = floatToFixed2(item.totalPrice) as unknown as number
return item
})
]
isSetListValue.value = true
},
{ immediate: true, deep: true }
)
</script> </script>
...@@ -10,22 +10,33 @@ ...@@ -10,22 +10,33 @@
</ContractDetailsHeader> </ContractDetailsHeader>
<el-col> <el-col>
<el-tabs> <el-tabs>
<!-- TODO @puhui999:跟进记录 --> <el-tab-pane label="跟进记录">
<FollowUpList :biz-id="contract.id" :biz-type="BizTypeEnum.CRM_CONTRACT" />
</el-tab-pane>
<el-tab-pane label="基本信息"> <el-tab-pane label="基本信息">
<ContractDetailsInfo :contract="contract" /> <ContractDetailsInfo :contract="contract" />
</el-tab-pane> </el-tab-pane>
<!-- TODO @puhui999:products 更合适哈 -->
<el-tab-pane label="产品"> <el-tab-pane label="产品">
<ContractProductList v-model="contract.productItems" /> <ContractProductList :contract="contract" />
</el-tab-pane>
<el-tab-pane label="回款">
<ReceivablePlanList
:contract-id="contract.id!"
:customer-id="contract.customerId"
@create-receivable="createReceivable"
/>
<ReceivableList
ref="receivableListRef"
:contract-id="contract.id!"
:customer-id="contract.customerId"
/>
</el-tab-pane> </el-tab-pane>
<!-- TODO @puhui999:回款信息 -->
<!-- TODO @puhui999:这里是不是不用 isPool 哈 -->
<el-tab-pane label="团队成员"> <el-tab-pane label="团队成员">
<PermissionList <PermissionList
ref="permissionListRef" ref="permissionListRef"
:biz-id="contract.id!" :biz-id="contract.id!"
:biz-type="BizTypeEnum.CRM_CONTRACT" :biz-type="BizTypeEnum.CRM_CONTRACT"
:show-action="!permissionListRef?.isPool || false" :show-action="false"
@quit-team="close" @quit-team="close"
/> />
</el-tab-pane> </el-tab-pane>
...@@ -43,16 +54,20 @@ ...@@ -43,16 +54,20 @@
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import { OperateLogV2VO } from '@/api/system/operatelog' import { OperateLogV2VO } from '@/api/system/operatelog'
import * as ContractApi from '@/api/crm/contract' import * as ContractApi from '@/api/crm/contract'
import ContractDetailsHeader from './ContractDetailsHeader.vue'
import ContractDetailsInfo from './ContractDetailsInfo.vue' import ContractDetailsInfo from './ContractDetailsInfo.vue'
import ContractDetailsHeader from './ContractDetailsHeader.vue'
import ContractProductList from './ContractProductList.vue' import ContractProductList from './ContractProductList.vue'
import { BizTypeEnum } from '@/api/crm/permission' import { BizTypeEnum } from '@/api/crm/permission'
import { getOperateLogPage } from '@/api/crm/operateLog' import { getOperateLogPage } from '@/api/crm/operateLog'
import ContractForm from '@/views/crm/contract/ContractForm.vue' import ContractForm from '@/views/crm/contract/ContractForm.vue'
import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue' import CrmTransferForm from '@/views/crm/permission/components/TransferForm.vue'
import PermissionList from '@/views/crm/permission/components/PermissionList.vue' import PermissionList from '@/views/crm/permission/components/PermissionList.vue'
import FollowUpList from '@/views/crm/followup/index.vue'
import ReceivableList from '@/views/crm/receivable/components/ReceivableList.vue'
import ReceivablePlanList from '@/views/crm/receivable/plan/components/ReceivablePlanList.vue'
defineOptions({ name: 'CrmContractDetail' }) defineOptions({ name: 'CrmContractDetail' })
const props = defineProps<{ id?: number }>()
const route = useRoute() const route = useRoute()
const message = useMessage() const message = useMessage()
...@@ -71,8 +86,8 @@ const openForm = (type: string, id?: number) => { ...@@ -71,8 +86,8 @@ const openForm = (type: string, id?: number) => {
const getContractData = async () => { const getContractData = async () => {
loading.value = true loading.value = true
try { try {
await getOperateLog(contractId.value)
contract.value = await ContractApi.getContract(contractId.value) contract.value = await ContractApi.getContract(contractId.value)
await getOperateLog(contractId.value)
} finally { } finally {
loading.value = false loading.value = false
} }
...@@ -91,8 +106,14 @@ const getOperateLog = async (contractId: number) => { ...@@ -91,8 +106,14 @@ const getOperateLog = async (contractId: number) => {
logList.value = data.list logList.value = data.list
} }
/** 从回款计划创建回款 */
const receivableListRef = ref<InstanceType<typeof ReceivableList>>() // 回款列表 Ref
const createReceivable = (planData: any) => {
receivableListRef.value?.createReceivable(planData)
}
/** 转移 */ /** 转移 */
// TODO @puhui999:这个组件,要不传递业务类型,然后组件里判断 title 和 api 能调用哪个;整体治理掉; // TODO @puhui999:这个组件,要不传递业务类型,然后组件里判断 title 和 api 能调用哪个;整体治理掉;好呢
const transferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 合同转移表单 ref const transferFormRef = ref<InstanceType<typeof CrmTransferForm>>() // 合同转移表单 ref
const transferContract = () => { const transferContract = () => {
transferFormRef.value?.open('合同转移', contract.value.id, ContractApi.transferContract) transferFormRef.value?.open('合同转移', contract.value.id, ContractApi.transferContract)
...@@ -107,7 +128,7 @@ const close = () => { ...@@ -107,7 +128,7 @@ const close = () => {
/** 初始化 */ /** 初始化 */
onMounted(async () => { onMounted(async () => {
const id = route.params.id const id = props.id || route.params.id
if (!id) { if (!id) {
message.warning('参数错误,合同不能为空!') message.warning('参数错误,合同不能为空!')
close() close()
......
...@@ -25,6 +25,24 @@ ...@@ -25,6 +25,24 @@
placeholder="请输入合同名称" placeholder="请输入合同名称"
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
/> />
<el-form-item label="客户" prop="customerId">
<el-select
v-model="queryParams.customerId"
class="!w-240px"
clearable
lable-key="name"
placeholder="请选择客户"
value-key="id"
@keyup.enter="handleQuery"
>
<el-option
v-for="item in customerList"
:key="item.id"
:label="item.name"
:value="item.id!"
/>
</el-select>
</el-form-item>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button @click="handleQuery"> <el-button @click="handleQuery">
...@@ -55,9 +73,20 @@ ...@@ -55,9 +73,20 @@
<!-- 列表 --> <!-- 列表 -->
<ContentWrap> <ContentWrap>
<el-tabs v-model="activeName" @tab-click="handleTabClick">
<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 v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
<el-table-column align="center" fixed="left" label="合同编号" prop="no" width="130" /> <el-table-column align="center" fixed="left" label="合同编号" prop="no" width="180" />
<el-table-column align="center" label="合同名称" prop="name" width="130" /> <el-table-column align="center" fixed="left" label="合同名称" prop="name" width="160">
<template #default="scope">
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
{{ scope.row.name }}
</el-link>
</template>
</el-table-column>
<el-table-column align="center" label="客户名称" prop="customerName" width="120"> <el-table-column align="center" label="客户名称" prop="customerName" width="120">
<template #default="scope"> <template #default="scope">
<el-link <el-link
...@@ -69,8 +98,24 @@ ...@@ -69,8 +98,24 @@
</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<!-- TODO @puhui999:做了商机详情后,可以把这个超链接加上 --> <el-table-column align="center" label="商机名称" prop="businessName" width="130">
<el-table-column align="center" label="商机名称" prop="businessName" width="130" /> <template #default="scope">
<el-link
:underline="false"
type="primary"
@click="openBusinessDetail(scope.row.businessId)"
>
{{ scope.row.businessName }}
</el-link>
</template>
</el-table-column>
<el-table-column
align="center"
label="合同金额(元)"
prop="totalPrice"
width="140"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column <el-table-column
align="center" align="center"
label="下单时间" label="下单时间"
...@@ -80,13 +125,6 @@ ...@@ -80,13 +125,6 @@
/> />
<el-table-column <el-table-column
align="center" align="center"
label="合同金额"
prop="price"
width="130"
:formatter="fenToYuanFormat"
/>
<el-table-column
align="center"
label="合同开始时间" label="合同开始时间"
prop="startTime" prop="startTime"
width="120" width="120"
...@@ -104,17 +142,41 @@ ...@@ -104,17 +142,41 @@
<el-link <el-link
:underline="false" :underline="false"
type="primary" type="primary"
@click="openContactDetail(scope.row.contactId)" @click="openContactDetail(scope.row.signContactId)"
> >
{{ scope.row.contactName }} {{ scope.row.signContactName }}
</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="公司签约人" prop="signUserName" width="130" /> <el-table-column align="center" label="公司签约人" prop="signUserName" width="130" />
<el-table-column align="center" label="备注" prop="remark" width="130" /> <el-table-column align="center" label="备注" prop="remark" width="200" />
<!-- TODO @puhui999:后续可加 【已收款金额】、【未收款金额】 --> <el-table-column
align="center"
label="已回款金额(元)"
prop="totalReceivablePrice"
width="140"
:formatter="erpPriceTableColumnFormatter"
/>
<el-table-column
align="center"
label="未回款金额(元)"
prop="totalReceivablePrice"
width="140"
:formatter="erpPriceTableColumnFormatter"
>
<template #default="scope">
{{ erpPriceInputFormatter(scope.row.totalPrice - scope.row.totalReceivablePrice) }}
</template>
</el-table-column>
<el-table-column
:formatter="dateFormatter"
align="center"
label="最后跟进时间"
prop="contactLastTime"
width="180px"
/>
<el-table-column align="center" label="负责人" prop="ownerUserName" width="120" /> <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="ownerUserDeptName" width="100px" />
<el-table-column <el-table-column
:formatter="dateFormatter" :formatter="dateFormatter"
align="center" align="center"
...@@ -129,6 +191,7 @@ ...@@ -129,6 +191,7 @@
prop="createTime" prop="createTime"
width="180px" width="180px"
/> />
<el-table-column align="center" label="创建人" prop="creatorName" width="120" />
<el-table-column align="center" fixed="right" label="合同状态" prop="auditStatus" width="120"> <el-table-column align="center" fixed="right" label="合同状态" prop="auditStatus" width="120">
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" /> <dict-tag :type="DICT_TYPE.CRM_AUDIT_STATUS" :value="scope.row.auditStatus" />
...@@ -137,6 +200,7 @@ ...@@ -137,6 +200,7 @@
<el-table-column fixed="right" label="操作" width="250"> <el-table-column fixed="right" label="操作" width="250">
<template #default="scope"> <template #default="scope">
<el-button <el-button
v-if="scope.row.auditStatus === 0"
v-hasPermi="['crm:contract:update']" v-hasPermi="['crm:contract:update']"
link link
type="primary" type="primary"
...@@ -144,8 +208,8 @@ ...@@ -144,8 +208,8 @@
> >
编辑 编辑
</el-button> </el-button>
<!-- TODO @puhui999:可以加下判断,什么情况下,可以审批;然后加个【查看审批】按钮 -->
<el-button <el-button
v-if="scope.row.auditStatus === 0"
v-hasPermi="['crm:contract:update']" v-hasPermi="['crm:contract:update']"
link link
type="primary" type="primary"
...@@ -154,6 +218,15 @@ ...@@ -154,6 +218,15 @@
提交审核 提交审核
</el-button> </el-button>
<el-button <el-button
v-else
link
v-hasPermi="['crm:contract:update']"
type="primary"
@click="handleProcessDetail(scope.row)"
>
查看审批
</el-button>
<el-button
v-hasPermi="['crm:contract:query']" v-hasPermi="['crm:contract:query']"
link link
type="primary" type="primary"
...@@ -189,8 +262,10 @@ import { dateFormatter, dateFormatter2 } from '@/utils/formatTime' ...@@ -189,8 +262,10 @@ import { dateFormatter, dateFormatter2 } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import * as ContractApi from '@/api/crm/contract' import * as ContractApi from '@/api/crm/contract'
import ContractForm from './ContractForm.vue' import ContractForm from './ContractForm.vue'
import { fenToYuanFormat } from '@/utils/formatter'
import { DICT_TYPE } from '@/utils/dict' import { DICT_TYPE } from '@/utils/dict'
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
import * as CustomerApi from '@/api/crm/customer'
import { TabsPaneContext } from 'element-plus'
defineOptions({ name: 'CrmContract' }) defineOptions({ name: 'CrmContract' })
...@@ -203,16 +278,22 @@ const list = ref([]) // 列表的数据 ...@@ -203,16 +278,22 @@ const list = ref([]) // 列表的数据
const queryParams = reactive({ const queryParams = reactive({
pageNo: 1, pageNo: 1,
pageSize: 10, pageSize: 10,
sceneType: '1', // 默认和 activeName 相等
name: null, name: null,
customerId: null, customerId: null,
businessId: null,
orderDate: [], orderDate: [],
no: null, no: null
discountPercent: null,
productPrice: null
}) })
const queryFormRef = ref() // 搜索的表单 const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中 const exportLoading = ref(false) // 导出的加载中
const activeName = ref('1') // 列表 tab
const customerList = ref<CustomerApi.CustomerVO[]>([]) // 客户列表
/** tab 切换 */
const handleTabClick = (tab: TabsPaneContext) => {
queryParams.sceneType = tab.paneName
handleQuery()
}
/** 查询列表 */ /** 查询列表 */
const getList = async () => { const getList = async () => {
...@@ -280,6 +361,11 @@ const handleSubmit = async (row: ContractApi.ContractVO) => { ...@@ -280,6 +361,11 @@ const handleSubmit = async (row: ContractApi.ContractVO) => {
await getList() await getList()
} }
/** 查看审批 */
const handleProcessDetail = (row: ContractApi.ContractVO) => {
push({ name: 'BpmProcessInstanceDetail', query: { id: row.processInstanceId } })
}
/** 打开合同详情 */ /** 打开合同详情 */
const { push } = useRouter() const { push } = useRouter()
const openDetail = (id: number) => { const openDetail = (id: number) => {
...@@ -296,8 +382,14 @@ const openContactDetail = (id: number) => { ...@@ -296,8 +382,14 @@ const openContactDetail = (id: number) => {
push({ name: 'CrmContactDetail', params: { id } }) push({ name: 'CrmContactDetail', params: { id } })
} }
/** 打开商机详情 */
const openBusinessDetail = (id: number) => {
push({ name: 'CrmBusinessDetail', params: { id } })
}
/** 初始化 **/ /** 初始化 **/
onMounted(() => { onMounted(async () => {
getList() await getList()
customerList.value = await CustomerApi.getCustomerSimpleList()
}) })
</script> </script>
<!-- TODO @puhui999:这个好像和 detail 重复了???能不能复用 detail 哈? -->
<template>
<el-form ref="formRef" v-loading="formLoading" :model="formData" label-width="110px">
<el-row>
<el-col :span="24" class="mb-10px">
<CardTitle title="基本信息" />
</el-col>
<el-col :span="12">
<el-form-item label="合同名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入合同名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同编号" prop="no">
<el-input v-model="formData.no" placeholder="请输入合同编号" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="客户" prop="customerId">
<el-select v-model="formData.customerId">
<el-option
v-for="item in customerList"
:key="item.id"
:label="item.name"
:value="item.id!"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="客户签约人" prop="contactId">
<el-select v-model="formData.contactId" :disabled="!formData.customerId">
<el-option
v-for="item in getContactOptions"
:key="item.id"
:label="item.name"
:value="item.id!"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司签约人" prop="signUserId">
<el-select v-model="formData.signUserId">
<el-option
v-for="item in userList"
:key="item.id"
:label="item.nickname"
:value="item.id!"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="负责人" prop="ownerUserId">
<el-select v-model="formData.ownerUserId">
<el-option
v-for="item in userList"
:key="item.id"
:label="item.nickname"
:value="item.id!"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="商机名称" prop="businessId">
<el-select v-model="formData.businessId">
<el-option
v-for="item in businessList"
:key="item.id"
:label="item.name"
:value="item.id!"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="合同金额(元)" prop="price">
<el-input v-model="formData.price" placeholder="请输入合同金额" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下单日期" prop="orderDate">
<el-date-picker
v-model="formData.orderDate"
placeholder="选择下单日期"
type="date"
value-format="x"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="开始时间" prop="startTime">
<el-date-picker
v-model="formData.startTime"
placeholder="选择开始时间"
type="date"
value-format="x"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="结束时间" prop="endTime">
<el-date-picker
v-model="formData.endTime"
placeholder="选择结束时间"
type="date"
value-format="x"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" :rows="3" placeholder="请输入备注" type="textarea" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="产品列表" prop="productList">
<ProductList v-model="formData.productItems" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="整单折扣(%)" prop="discountPercent">
<el-input v-model="formData.discountPercent" placeholder="请输入整单折扣" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品总金额(元)" prop="productPrice">
<el-input v-model="formData.productPrice" placeholder="请输入产品总金额" />
</el-form-item>
</el-col>
<el-col :span="24">
<CardTitle class="mb-10px" title="审批信息" />
</el-col>
<el-col :span="12">
<el-button
class="m-20px"
link
type="primary"
@click="BPMLModelRef?.handleBpmnDetail('contract-approve')"
>
查看工作流
</el-button>
</el-col>
</el-row>
</el-form>
<BPMLModel ref="BPMLModelRef" />
</template>
<script lang="ts" setup>
import * as CustomerApi from '@/api/crm/customer'
import * as ContractApi from '@/api/crm/contract'
import * as UserApi from '@/api/system/user'
import * as ContactApi from '@/api/crm/contact'
import * as BusinessApi from '@/api/crm/business'
import ProductList from '@/views/crm/contract/components/ProductList.vue'
// import BPMLModel from '@/views/crm/contract/components/BPMLModel.vue'
defineOptions({ name: 'ContractDetailOA' })
const props = defineProps<{ id?: number }>()
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formData = ref<ContractApi.ContractVO>({} as ContractApi.ContractVO)
const formRef = ref() // 表单 Ref
const BPMLModelRef = ref<InstanceType<typeof BPMLModel>>()
watch(
() => formData.value.productItems,
(val) => {
if (!val || val.length === 0) {
formData.value.productPrice = 0
return
}
// 使用reduce函数进行累加
formData.value.productPrice = val.reduce(
(accumulator, currentValue) =>
isNaN(accumulator + currentValue.totalPrice) ? 0 : accumulator + currentValue.totalPrice,
0
)
},
{ deep: true }
)
/** 打开弹窗 */
const getFormData = async () => {
await getAllApi()
formLoading.value = true
try {
formData.value = await ContractApi.getContract(props.id!)
} finally {
formLoading.value = false
}
}
const getAllApi = async () => {
await Promise.all([getCustomerList(), getUserList(), getContactListList(), getBusinessList()])
}
const customerList = ref<CustomerApi.CustomerVO[]>([])
/** 获取客户 */
const getCustomerList = async () => {
customerList.value = await CustomerApi.getCustomerSimpleList()
}
const contactList = ref<ContactApi.ContactVO[]>([])
/** 动态获取客户联系人 */
const getContactOptions = computed(() =>
contactList.value.filter((item) => item.customerId === formData.value.customerId)
)
const getContactListList = async () => {
contactList.value = await ContactApi.getSimpleContactList()
}
const userList = ref<UserApi.UserVO[]>([])
/** 获取用户列表 */
const getUserList = async () => {
userList.value = await UserApi.getSimpleUserList()
}
const businessList = ref<BusinessApi.BusinessVO[]>([])
/** 获取商机 */
const getBusinessList = async () => {
businessList.value = await BusinessApi.getSimpleBusinessList()
}
onMounted(() => {
getFormData()
})
</script>
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
<el-upload <el-upload
ref="uploadRef" ref="uploadRef"
v-model:file-list="fileList" v-model:file-list="fileList"
:action="importUrl + '?updateSupport=' + updateSupport"
:auto-upload="false" :auto-upload="false"
:disabled="formLoading" :disabled="formLoading"
:headers="uploadHeaders" :headers="uploadHeaders"
...@@ -13,6 +12,7 @@ ...@@ -13,6 +12,7 @@
:on-exceed="handleExceed" :on-exceed="handleExceed"
:on-success="submitFormSuccess" :on-success="submitFormSuccess"
accept=".xlsx, .xls" accept=".xlsx, .xls"
action="none"
drag drag
> >
<Icon icon="ep:upload" /> <Icon icon="ep:upload" />
...@@ -45,6 +45,7 @@ ...@@ -45,6 +45,7 @@
import * as CustomerApi from '@/api/crm/customer' import * as CustomerApi from '@/api/crm/customer'
import { getAccessToken, getTenantId } from '@/utils/auth' import { getAccessToken, getTenantId } from '@/utils/auth'
import download from '@/utils/download' import download from '@/utils/download'
import type { UploadUserFile } from 'element-plus'
defineOptions({ name: 'SystemUserImportForm' }) defineOptions({ name: 'SystemUserImportForm' })
...@@ -53,11 +54,9 @@ const message = useMessage() // 消息弹窗 ...@@ -53,11 +54,9 @@ const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中 const formLoading = ref(false) // 表单的加载中
const uploadRef = ref() const uploadRef = ref()
const importUrl =
import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/crm/customer/import'
const uploadHeaders = ref() // 上传 Header 头 const uploadHeaders = ref() // 上传 Header 头
const fileList = ref([]) // 文件列表 const fileList = ref<UploadUserFile[]>([]) // 文件列表
const updateSupport = ref(0) // 是否更新已经存在的客户数据 const updateSupport = ref(false) // 是否更新已经存在的客户数据
/** 打开弹窗 */ /** 打开弹窗 */
const open = () => { const open = () => {
...@@ -79,7 +78,11 @@ const submitForm = async () => { ...@@ -79,7 +78,11 @@ const submitForm = async () => {
'tenant-id': getTenantId() 'tenant-id': getTenantId()
} }
formLoading.value = true formLoading.value = true
uploadRef.value!.submit() const formData = new FormData()
formData.append('updateSupport', updateSupport.value)
formData.append('file', fileList.value[0].raw)
// TODO @芋艿:后面是不是可以采用这种形式,去掉 uploadHeaders
await CustomerApi.handleImport(formData)
} }
/** 文件上传成功 */ /** 文件上传成功 */
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
> >
锁定 锁定
</el-button> </el-button>
<el-button v-if="!customer.ownerUserId" type="primary" @click="handleReceive"> 领取 </el-button> <el-button v-if="!customer.ownerUserId" type="primary" @click="handleReceive"> 领取</el-button>
<el-button v-if="!customer.ownerUserId" type="primary" @click="handleDistributeForm"> <el-button v-if="!customer.ownerUserId" type="primary" @click="handleDistributeForm">
分配 分配
</el-button> </el-button>
...@@ -64,8 +64,8 @@ ...@@ -64,8 +64,8 @@
<ContractList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" /> <ContractList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="回款" lazy> <el-tab-pane label="回款" lazy>
<ReceivablePlanList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" /> <ReceivablePlanList :customer-id="customer.id!" @create-receivable="createReceivable" />
<ReceivableList :biz-id="customer.id!" :biz-type="BizTypeEnum.CRM_CUSTOMER" /> <ReceivableList ref="receivableListRef" :customer-id="customer.id!" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="操作日志"> <el-tab-pane label="操作日志">
<OperateLogV2 :log-list="logList" /> <OperateLogV2 :log-list="logList" />
...@@ -103,7 +103,7 @@ const customerId = ref(0) // 客户编号 ...@@ -103,7 +103,7 @@ const customerId = ref(0) // 客户编号
const loading = ref(true) // 加载中 const loading = ref(true) // 加载中
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
const { delView } = useTagsViewStore() // 视图操作 const { delView } = useTagsViewStore() // 视图操作
const { currentRoute } = useRouter() // 路由 const { push, currentRoute } = useRouter() // 路由
const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref const permissionListRef = ref<InstanceType<typeof PermissionList>>() // 团队成员列表 Ref
...@@ -180,6 +180,7 @@ const handlePutPool = async () => { ...@@ -180,6 +180,7 @@ const handlePutPool = async () => {
await message.confirm(`确定将客户【${customer.value.name}】放入公海吗?`) await message.confirm(`确定将客户【${customer.value.name}】放入公海吗?`)
await CustomerApi.putCustomerPool(unref(customerId.value)) await CustomerApi.putCustomerPool(unref(customerId.value))
message.success(`客户【${customer.value.name}】放入公海成功`) message.success(`客户【${customer.value.name}】放入公海成功`)
// 加载
close() close()
} }
...@@ -196,8 +197,15 @@ const getOperateLog = async () => { ...@@ -196,8 +197,15 @@ const getOperateLog = async () => {
logList.value = data.list logList.value = data.list
} }
/** 从回款计划创建回款 */
const receivableListRef = ref<InstanceType<typeof ReceivableList>>() // 回款列表 Ref
const createReceivable = (planData: any) => {
receivableListRef.value?.createReceivable(planData)
}
const close = () => { const close = () => {
delView(unref(currentRoute)) delView(unref(currentRoute))
push({ name: 'CrmCustomer' })
} }
/** 初始化 */ /** 初始化 */
......
...@@ -8,16 +8,14 @@ ...@@ -8,16 +8,14 @@
v-loading="formLoading" v-loading="formLoading"
> >
<el-form-item label="规则适用人群" prop="userIds"> <el-form-item label="规则适用人群" prop="userIds">
<el-tree-select <el-select multiple filterable v-model="formData.userIds">
v-model="formData.userIds" <el-option
:data="userTree" v-for="item in userOptions"
:props="defaultProps" :key="item.id"
multiple :label="item.nickname"
filterable :value="item.id"
check-on-click-node />
node-key="id" </el-select>
placeholder="请选择规则适用人群"
/>
</el-form-item> </el-form-item>
<el-form-item label="规则适用部门" prop="deptIds"> <el-form-item label="规则适用部门" prop="deptIds">
<el-tree-select <el-tree-select
...@@ -62,6 +60,7 @@ import { defaultProps, handleTree } from '@/utils/tree' ...@@ -62,6 +60,7 @@ import { defaultProps, handleTree } from '@/utils/tree'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
import { cloneDeep } from 'lodash-es' import { cloneDeep } from 'lodash-es'
import { LimitConfType } from '@/api/crm/customer/limitConfig' import { LimitConfType } from '@/api/crm/customer/limitConfig'
import { aw } from '../../../../../dist-prod/assets/index-9eac537b'
const { t } = useI18n() // 国际化 const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗 const message = useMessage() // 消息弹窗
...@@ -85,7 +84,7 @@ const formRules = reactive({ ...@@ -85,7 +84,7 @@ const formRules = reactive({
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
// TODO @芋艿:看看怎么搞个部门选择组件 // TODO @芋艿:看看怎么搞个部门选择组件
const deptTree = ref() // 部门树形结构 const deptTree = ref() // 部门树形结构
const userTree = ref() // 用户树形结构 const userOptions = ref<UserApi.UserVO[]>([]) // 用户列表
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (type: string, limitConfType: LimitConfType, id?: number) => { const open = async (type: string, limitConfType: LimitConfType, id?: number) => {
...@@ -105,9 +104,9 @@ const open = async (type: string, limitConfType: LimitConfType, id?: number) => ...@@ -105,9 +104,9 @@ const open = async (type: string, limitConfType: LimitConfType, id?: number) =>
formData.value.type = limitConfType formData.value.type = limitConfType
} }
// 获得部门树 // 获得部门树
await getDeptTree() deptTree.value = handleTree(await DeptApi.getSimpleDeptList())
// 获得用户 // 获得用户
await getUserTree() userOptions.value = await UserApi.getSimpleUserList()
} }
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
...@@ -149,76 +148,4 @@ const resetForm = () => { ...@@ -149,76 +148,4 @@ const resetForm = () => {
} }
formRef.value?.resetFields() formRef.value?.resetFields()
} }
/**
* 获取部门树
*/
const getDeptTree = async () => {
const res = await DeptApi.getSimpleDeptList()
deptTree.value = []
deptTree.value.push(...handleTree(res))
}
/**
* 获取用户树
*/
const getUserTree = async () => {
const res = await UserApi.getAllUser()
userTree.value = []
userTree.value = cloneDeep(unref(deptTree))
const deptUserMap = {}
res.forEach((user) => {
if (user.dept) {
if (!deptUserMap[user.deptId]) {
deptUserMap[user.deptId] = []
}
deptUserMap[user.deptId].push(user)
}
})
handleUserData(userTree.value, deptUserMap)
}
// TODO @芋艿:看看怎么搞个用户选择的组件
/**
* 处理用户树
*
* @param deptTree
* @param deptUserMap
*/
const handleUserData = (deptTree, deptUserMap) => {
for (let i = 0; i < deptTree.length; i++) {
// 如果是用户,就不用继续找部门下的用户
if (deptTree[i].isUser) {
continue
}
const users = deptUserMap[deptTree[i].id]
if (users) {
if (!deptTree[i].children) {
deptTree[i].children = []
}
deptTree[i].children.push(
...users.map((user) => {
return {
id: user.id,
name: user.username + '-' + user.nickname,
isUser: true,
// 用户状态为关闭
disabled: user.status === 1
}
})
)
}
if (deptTree[i].children && deptTree[i].children.length !== 0) {
handleUserData(deptTree[i].children, deptUserMap)
}
// 非人员选项禁用
deptTree[i].disabled = true
// 将非人员的 id 置为空
deptTree[i].id = 'null'
}
}
</script> </script>
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
class="mt-4" class="mt-4"
> >
<el-table-column label="编号" align="center" prop="id" /> <el-table-column label="编号" align="center" prop="id" />
<el-table-column label="规则类型" align="center" prop="type" />
<el-table-column <el-table-column
label="规则适用人群" label="规则适用人群"
align="center" align="center"
...@@ -39,6 +38,7 @@ ...@@ -39,6 +38,7 @@
label="成交客户是否占用拥有客户数" label="成交客户是否占用拥有客户数"
align="center" align="center"
prop="dealCountEnabled" prop="dealCountEnabled"
min-width="100"
> >
<template #default="scope"> <template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealCountEnabled" /> <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealCountEnabled" />
......
<!-- 跟进记录的添加表单弹窗 --> <!-- 跟进记录的添加表单弹窗 -->
<template> <template>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="50%"> <Dialog v-model="dialogVisible" title="添加跟进记录" width="50%">
<el-form <el-form
ref="formRef" ref="formRef"
v-loading="formLoading" v-loading="formLoading"
...@@ -36,32 +36,32 @@ ...@@ -36,32 +36,32 @@
<el-input v-model="formData.content" :rows="3" type="textarea" /> <el-input v-model="formData.content" :rows="3" type="textarea" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="12">
<el-form-item label="图片" prop="content"> <el-form-item label="图片" prop="picUrls">
<UploadImgs v-model="formData.picUrls" class="min-w-80px" /> <UploadImgs v-model="formData.picUrls" class="min-w-80px" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="12">
<el-form-item label="附件" prop="content"> <el-form-item label="附件" prop="fileUrls">
<UploadFile v-model="formData.fileUrls" class="min-w-80px" /> <UploadFile v-model="formData.fileUrls" class="min-w-80px" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24" v-if="formData.bizType == BizTypeEnum.CRM_CUSTOMER">
<el-form-item label="关联联系人" prop="contactIds"> <el-form-item label="关联联系人" prop="contactIds">
<el-button @click="handleAddContact"> <el-button @click="handleOpenContact">
<Icon class="mr-5px" icon="ep:plus" /> <Icon class="mr-5px" icon="ep:plus" />
添加联系人 添加联系人
</el-button> </el-button>
<contact-list v-model:contactIds="formData.contactIds" /> <FollowUpRecordContactForm :contacts="formData.contacts" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24" v-if="formData.bizType == BizTypeEnum.CRM_CUSTOMER">
<el-form-item label="关联商机" prop="businessIds"> <el-form-item label="关联商机" prop="businessIds">
<el-button @click="handleAddBusiness"> <el-button @click="handleOpenBusiness">
<Icon class="mr-5px" icon="ep:plus" /> <Icon class="mr-5px" icon="ep:plus" />
添加商机 添加商机
</el-button> </el-button>
<business-list v-model:businessIds="formData.businessIds" /> <FollowUpRecordBusinessForm :businesses="formData.businesses" />
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
...@@ -71,13 +71,29 @@ ...@@ -71,13 +71,29 @@
<el-button @click="dialogVisible = false">取 消</el-button> <el-button @click="dialogVisible = false">取 消</el-button>
</template> </template>
</Dialog> </Dialog>
<ContactTableSelect ref="contactTableSelectRef" v-model="formData.contactIds" />
<BusinessTableSelect ref="businessTableSelectRef" v-model="formData.businessIds" /> <!-- 弹窗 -->
<ContactListModal
ref="contactTableSelectRef"
:customer-id="formData.bizId"
@success="handleAddContact"
/>
<BusinessListModal
ref="businessTableSelectRef"
:customer-id="formData.bizId"
@success="handleAddBusiness"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup' import { FollowUpRecordApi, FollowUpRecordVO } from '@/api/crm/followup'
import { BusinessList, BusinessTableSelect, ContactList, ContactTableSelect } from './components' import { BizTypeEnum } from '@/api/crm/permission'
import FollowUpRecordBusinessForm from './components/FollowUpRecordBusinessForm.vue'
import FollowUpRecordContactForm from './components/FollowUpRecordContactForm.vue'
import BusinessListModal from '@/views/crm/business/components/BusinessListModal.vue'
import * as BusinessApi from '@/api/crm/business'
import ContactListModal from '@/views/crm/contact/components/ContactListModal.vue'
import * as ContactApi from '@/api/crm/contact'
defineOptions({ name: 'FollowUpRecordForm' }) defineOptions({ name: 'FollowUpRecordForm' })
...@@ -87,8 +103,12 @@ const message = useMessage() // 消息弹窗 ...@@ -87,8 +103,12 @@ const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示 const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题 const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改 const formData = ref({
const formData = ref<FollowUpRecordVO>({} as FollowUpRecordVO) bizType: undefined,
bizId: undefined,
businesses: [],
contacts: []
})
const formRules = reactive({ const formRules = reactive({
type: [{ required: true, message: '跟进类型不能为空', trigger: 'change' }], type: [{ required: true, message: '跟进类型不能为空', trigger: 'change' }],
content: [{ required: true, message: '跟进内容不能为空', trigger: 'blur' }], content: [{ required: true, message: '跟进内容不能为空', trigger: 'blur' }],
...@@ -98,22 +118,11 @@ const formRules = reactive({ ...@@ -98,22 +118,11 @@ const formRules = reactive({
const formRef = ref() // 表单 Ref const formRef = ref() // 表单 Ref
/** 打开弹窗 */ /** 打开弹窗 */
const open = async (bizType: number, bizId: number, type: string, id?: number) => { const open = async (bizType: number, bizId: number) => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm() resetForm()
formData.value.bizType = bizType formData.value.bizType = bizType
formData.value.bizId = bizId formData.value.bizId = bizId
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await FollowUpRecordApi.getFollowUpRecord(id)
} finally {
formLoading.value = false
}
}
} }
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 defineExpose({ open }) // 提供 open 方法,用于打开弹窗
...@@ -125,14 +134,13 @@ const submitForm = async () => { ...@@ -125,14 +134,13 @@ const submitForm = async () => {
// 提交请求 // 提交请求
formLoading.value = true formLoading.value = true
try { try {
const data = formData.value as unknown as FollowUpRecordVO const data = {
if (formType.value === 'create') { ...formData.value,
await FollowUpRecordApi.createFollowUpRecord(data) contactIds: formData.value.contacts.map((item) => item.id),
message.success(t('common.createSuccess')) businessIds: formData.value.businesses.map((item) => item.id)
} else { } as unknown as FollowUpRecordVO
await FollowUpRecordApi.updateFollowUpRecord(data) await FollowUpRecordApi.createFollowUpRecord(data)
message.success(t('common.updateSuccess')) message.success(t('common.createSuccess'))
}
dialogVisible.value = false dialogVisible.value = false
// 发送操作成功的事件 // 发送操作成功的事件
emit('success') emit('success')
...@@ -142,20 +150,39 @@ const submitForm = async () => { ...@@ -142,20 +150,39 @@ const submitForm = async () => {
} }
/** 关联联系人 */ /** 关联联系人 */
const contactTableSelectRef = ref<InstanceType<typeof ContactTableSelect>>() const contactTableSelectRef = ref<InstanceType<typeof ContactListModal>>()
const handleAddContact = () => { const handleOpenContact = () => {
contactTableSelectRef.value?.open() contactTableSelectRef.value?.open()
} }
const handleAddContact = (contactId: [], newContacts: ContactApi.ContactVO[]) => {
newContacts.forEach((contact) => {
if (!formData.value.contacts.some((item) => item.id === contact.id)) {
formData.value.contacts.push(contact)
}
})
}
/** 关联商机 */ /** 关联商机 */
const businessTableSelectRef = ref<InstanceType<typeof BusinessTableSelect>>() const businessTableSelectRef = ref<InstanceType<typeof BusinessListModal>>()
const handleAddBusiness = () => { const handleOpenBusiness = () => {
businessTableSelectRef.value?.open() businessTableSelectRef.value?.open()
} }
const handleAddBusiness = (businessId: [], newBusinesses: BusinessApi.BusinessVO[]) => {
newBusinesses.forEach((business) => {
if (!formData.value.businesses.some((item) => item.id === business.id)) {
formData.value.businesses.push(business)
}
})
}
/** 重置表单 */ /** 重置表单 */
const resetForm = () => { const resetForm = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
formData.value = {} as FollowUpRecordVO formData.value = {
bizId: undefined,
bizType: undefined,
businesses: [],
contacts: []
}
} }
</script> </script>
<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(unref(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>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment