Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
B
baifang-java
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
单欣鑫
baifang-java
Commits
882f447d
Commit
882f447d
authored
Jun 05, 2025
by
法拉51246
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
打印烂的
大屏没写 腾讯地图key替换为客户的
parent
29054b48
Changes
25
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
704 additions
and
39 deletions
+704
-39
pom.xml
yudao-module-visit/pom.xml
+6
-0
CustomerInfoController.java
...controller/admin/customerinfo/CustomerInfoController.java
+2
-0
CustomerInfoRespVO.java
.../controller/admin/customerinfo/vo/CustomerInfoRespVO.java
+7
-4
CustomerInfoSaveReqVO.java
...ntroller/admin/customerinfo/vo/CustomerInfoSaveReqVO.java
+2
-2
InfoController.java
...ao/module/visit/controller/admin/info/InfoController.java
+20
-0
InfoPrintVO.java
...ao/module/visit/controller/admin/info/vo/InfoPrintVO.java
+55
-0
InfoRespVO.java
...dao/module/visit/controller/admin/info/vo/InfoRespVO.java
+8
-3
InfoSaveReqVO.java
.../module/visit/controller/admin/info/vo/InfoSaveReqVO.java
+5
-2
InfoDO.java
...ocoder/yudao/module/visit/dal/dataobject/info/InfoDO.java
+6
-0
InfoMapper.java
...iocoder/yudao/module/visit/dal/mysql/info/InfoMapper.java
+4
-0
ErrorCodeConstants.java
.../iocoder/yudao/module/visit/enums/ErrorCodeConstants.java
+2
-0
CustomerInfoServiceImpl.java
...e/visit/service/customerinfo/CustomerInfoServiceImpl.java
+46
-0
InfoService.java
.../iocoder/yudao/module/visit/service/info/InfoService.java
+14
-1
InfoServiceImpl.java
...oder/yudao/module/visit/service/info/InfoServiceImpl.java
+134
-0
package-lock.json
yudao-ui/yudao-ui-admin-vue3/package-lock.json
+10
-2
package.json
yudao-ui/yudao-ui-admin-vue3/package.json
+2
-1
index.ts
yudao-ui/yudao-ui-admin-vue3/src/api/visit/info/index.ts
+43
-0
UserForm.vue
...ui/yudao-ui-admin-vue3/src/views/system/user/UserForm.vue
+0
-1
CustomerInfoForm.vue
...in-vue3/src/views/visit/customerinfo/CustomerInfoForm.vue
+3
-3
index.vue
...udao-ui-admin-vue3/src/views/visit/customerinfo/index.vue
+55
-9
InfoForm.vue
...-ui/yudao-ui-admin-vue3/src/views/visit/info/InfoForm.vue
+16
-4
index.vue
yudao-ui/yudao-ui-admin-vue3/src/views/visit/info/index.vue
+43
-5
index.vue
...-ui/yudao-ui-admin-vue3/src/views/visit/product/index.vue
+1
-1
MapPicker.vue
...ui/yudao-ui-admin-vue3/src/views/visit/util/MapPicker.vue
+1
-1
VisitPrint.vue
...i/yudao-ui-admin-vue3/src/views/visit/util/VisitPrint.vue
+219
-0
No files found.
yudao-module-visit/pom.xml
View file @
882f447d
...
...
@@ -45,6 +45,12 @@
<groupId>
cn.iocoder.boot
</groupId>
<artifactId>
yudao-spring-boot-starter-biz-data-permission
</artifactId>
</dependency>
<dependency>
<groupId>
cn.iocoder.boot
</groupId>
<artifactId>
yudao-module-infra-biz
</artifactId>
<version>
2.4.2-jdk8-SNAPSHOT
</version>
<scope>
compile
</scope>
</dependency>
</dependencies>
</project>
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/CustomerInfoController.java
View file @
882f447d
...
...
@@ -129,4 +129,6 @@ public class CustomerInfoController {
BeanUtils
.
toBean
(
list
,
CustomerInfoRespVO
.
class
));
}
}
\ No newline at end of file
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/vo/CustomerInfoRespVO.java
View file @
882f447d
...
...
@@ -24,8 +24,8 @@ public class CustomerInfoRespVO {
@ExcelProperty
(
"客户姓名"
)
private
String
customerName
;
@Schema
(
description
=
"联系方式"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@ExcelProperty
(
"联系方式"
)
@Schema
(
description
=
"联系方式
(客户手机号)
"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@ExcelProperty
(
"联系方式
(客户手机号)
"
)
private
String
contact
;
@Schema
(
description
=
"公司名称"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
...
...
@@ -69,12 +69,11 @@ public class CustomerInfoRespVO {
private
String
productNames
;
@Schema
(
description
=
"客户所属部门"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@ExcelProperty
(
"客户所属部门"
)
@ExcelProperty
(
value
=
"客户所属部门"
,
converter
=
DictConvert
.
class
)
@DictFormat
(
"customer_dept"
)
private
String
department
;
@Schema
(
description
=
"静态图"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@ExcelProperty
(
"静态图"
)
private
String
locationImage
;
@Schema
(
description
=
"创建时间"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
...
...
@@ -84,4 +83,8 @@ public class CustomerInfoRespVO {
@Schema
(
description
=
"产品信息List对象"
)
private
List
<
ProductSimpleReqVO
>
productList
;
@Schema
(
description
=
"业务经理"
)
@ExcelProperty
(
"业务经理"
)
private
String
creator
;
}
\ No newline at end of file
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/customerinfo/vo/CustomerInfoSaveReqVO.java
View file @
882f447d
...
...
@@ -17,8 +17,8 @@ public class CustomerInfoSaveReqVO {
@NotEmpty
(
message
=
"客户姓名不能为空"
)
private
String
customerName
;
@Schema
(
description
=
"联系方式"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@
NotEmpty
(
message
=
"联系方式不能为空
"
)
@Schema
(
description
=
"联系方式"
)
@
Pattern
(
regexp
=
"^$|^\\d{11}$"
,
message
=
"联系方式必须是11位数字
"
)
private
String
contact
;
@Schema
(
description
=
"公司名称"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
...
...
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/info/InfoController.java
View file @
882f447d
...
...
@@ -71,6 +71,26 @@ public class InfoController {
return
success
(
BeanUtils
.
toBean
(
info
,
InfoRespVO
.
class
));
}
@GetMapping
(
"/getPrintListByIds"
)
@Operation
(
summary
=
"获得客户拜访打印信息根据Ids"
)
public
CommonResult
<
List
<
InfoPrintVO
>>
getPrintListByIds
(
@RequestParam
(
"ids"
)
String
ids
)
{
String
[]
split
=
ids
.
split
(
","
);
//转为List
List
<
Long
>
idList
=
new
ArrayList
<>();
for
(
String
s
:
split
)
{
idList
.
add
(
Long
.
parseLong
(
s
));
}
List
<
InfoPrintVO
>
info
=
infoService
.
getInfoByIds
(
idList
);
return
success
(
info
);
}
@GetMapping
(
"/getPrintListByCompanyName"
)
@Operation
(
summary
=
"获得客户拜访打印信息根据公司名称"
)
public
CommonResult
<
InfoPrintVO
>
getPrintListByCompanyName
(
@RequestParam
(
"companyName"
)
String
companyName
)
{
InfoPrintVO
info
=
infoService
.
getInfoByCompanyName
(
companyName
);
return
success
(
info
);
}
@GetMapping
(
"/page"
)
@Operation
(
summary
=
"获得客户拜访记录分页"
)
@PreAuthorize
(
"@ss.hasPermission('visit:info:query')"
)
...
...
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/info/vo/InfoPrintVO.java
0 → 100644
View file @
882f447d
package
cn
.
iocoder
.
yudao
.
module
.
visit
.
controller
.
admin
.
info
.
vo
;
import
cn.iocoder.yudao.framework.excel.core.annotations.DictFormat
;
import
cn.iocoder.yudao.framework.excel.core.convert.DictConvert
;
import
com.alibaba.excel.annotation.ExcelIgnoreUnannotated
;
import
com.alibaba.excel.annotation.ExcelProperty
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
lombok.AllArgsConstructor
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
java.math.BigDecimal
;
import
java.time.LocalDateTime
;
import
java.util.List
;
@Schema
(
description
=
"管理后台 - 客户拜访记录 Print VO"
)
@Data
@NoArgsConstructor
@AllArgsConstructor
public
class
InfoPrintVO
{
@Schema
(
description
=
"联系方式"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
private
String
contact
;
@Schema
(
description
=
"客户公司名称"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
private
String
companyName
;
@Schema
(
description
=
"客户部门"
)
private
String
department
;
@Schema
(
description
=
"定位静态图"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
private
String
locationImage
;
@Schema
(
description
=
"最近四次拜访日期"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
private
List
<
LocalDateTime
>
visitDate
;
@Schema
(
description
=
"拜访品种"
)
private
String
visitProductNames
;
@Schema
(
description
=
"最近四次服务内容"
)
private
List
<
String
>
serviceContent
;
@Schema
(
description
=
"服务记录图片URL列表(JSON数组)"
)
private
String
serviceImages
;
@Schema
(
description
=
"业务员"
)
private
String
salesman
;
@Schema
(
description
=
"服务数量"
)
private
Integer
serviceCount
;
}
\ No newline at end of file
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/info/vo/InfoRespVO.java
View file @
882f447d
...
...
@@ -37,6 +37,11 @@ public class InfoRespVO {
@ExcelProperty
(
"客户公司名称"
)
private
String
companyName
;
@Schema
(
description
=
"客户所属部门"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@ExcelProperty
(
value
=
"客户所属部门"
,
converter
=
DictConvert
.
class
)
@DictFormat
(
"customer_dept"
)
private
String
department
;
@Schema
(
description
=
"所在地区"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@ExcelProperty
(
"所在地区"
)
private
String
regionFullName
;
...
...
@@ -50,7 +55,8 @@ public class InfoRespVO {
@Schema
(
description
=
"区名称"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
private
String
areaName
;
@Schema
(
description
=
"定位地址文字描述"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@Schema
(
description
=
"详细地址"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@ExcelProperty
(
"详细地址"
)
private
String
locationText
;
@Schema
(
description
=
"经度"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
...
...
@@ -59,7 +65,7 @@ public class InfoRespVO {
@Schema
(
description
=
"纬度"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
private
BigDecimal
latitude
;
@Schema
(
description
=
"定位静态图
URL
"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@Schema
(
description
=
"定位静态图"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
private
String
locationImage
;
@Schema
(
description
=
"拜访日期"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
...
...
@@ -101,7 +107,6 @@ public class InfoRespVO {
private
String
customerFeedback
;
@Schema
(
description
=
"服务记录图片URL列表(JSON数组)"
)
@ExcelProperty
(
"服务记录图片URL列表(JSON数组)"
)
private
String
serviceImages
;
...
...
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/controller/admin/info/vo/InfoSaveReqVO.java
View file @
882f447d
...
...
@@ -20,8 +20,8 @@ public class InfoSaveReqVO {
@NotEmpty
(
message
=
"客户姓名不能为空"
)
private
String
customerName
;
@Schema
(
description
=
"联系方式(客户手机号)"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
@
NotEmpty
(
message
=
"联系方式(客户手机号)不能为空
"
)
@Schema
(
description
=
"联系方式(客户手机号)"
)
@
Pattern
(
regexp
=
"^$|^\\d{11}$"
,
message
=
"联系方式必须是11位数字
"
)
private
String
contact
;
@Schema
(
description
=
"客户公司名称"
,
requiredMode
=
Schema
.
RequiredMode
.
REQUIRED
)
...
...
@@ -80,6 +80,9 @@ public class InfoSaveReqVO {
@Schema
(
description
=
"拜访类型(字典)"
,
example
=
"2"
)
private
Integer
visitType
;
@Schema
(
description
=
"客户所属部门(字典)"
)
private
String
department
;
@Schema
(
description
=
"服务内容"
)
private
String
serviceContent
;
...
...
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/dal/dataobject/info/InfoDO.java
View file @
882f447d
...
...
@@ -115,6 +115,12 @@ public class InfoDO extends BaseDO {
* 枚举 {@link TODO visit_type 对应的类}
*/
private
Integer
visitType
;
/**
* 客户部门
*
* 枚举 {@link TODO customer_dept 对应的类}
*/
private
String
department
;
/**
* 服务内容
*/
...
...
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/dal/mysql/info/InfoMapper.java
View file @
882f447d
...
...
@@ -31,4 +31,8 @@ public interface InfoMapper extends BaseMapperX<InfoDO> {
.
orderByDesc
(
InfoDO:
:
getId
));
}
default
List
<
InfoDO
>
selectByCompanyName
(
String
companyName
)
{
return
selectList
(
new
LambdaQueryWrapperX
<
InfoDO
>()
.
eq
(
InfoDO:
:
getCompanyName
,
companyName
));
}
}
\ No newline at end of file
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/enums/ErrorCodeConstants.java
View file @
882f447d
...
...
@@ -16,6 +16,8 @@ public interface ErrorCodeConstants {
// ========== 客户信息 ==========
ErrorCode
CUSTOMER_INFO_NOT_EXISTS
=
new
ErrorCode
(
1002000001
,
"客户信息不存在"
);
ErrorCode
CUSTOMER_INFO_COMPANY_NAME_DUPLICATE
=
new
ErrorCode
(
1002000002
,
"该公司名称已存在"
);
// ========== 客户拜访记录 ==========
ErrorCode
INFO_NOT_EXISTS
=
new
ErrorCode
(
1003000001
,
"客户拜访记录不存在"
);
...
...
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/customerinfo/CustomerInfoServiceImpl.java
View file @
882f447d
package
cn
.
iocoder
.
yudao
.
module
.
visit
.
service
.
customerinfo
;
import
cn.iocoder.yudao.module.infra.service.file.FileService
;
import
org.apache.http.HttpEntity
;
import
org.apache.http.client.methods.CloseableHttpResponse
;
import
org.apache.http.client.methods.HttpGet
;
import
org.apache.http.entity.ContentType
;
import
org.apache.http.impl.client.CloseableHttpClient
;
import
org.apache.http.impl.client.HttpClients
;
import
org.apache.http.util.EntityUtils
;
import
org.springframework.stereotype.Service
;
import
javax.annotation.Resource
;
import
org.springframework.validation.annotation.Validated
;
import
org.springframework.transaction.annotation.Transactional
;
import
java.io.IOException
;
import
java.util.*
;
import
cn.iocoder.yudao.module.visit.controller.admin.customerinfo.vo.*
;
import
cn.iocoder.yudao.module.visit.dal.dataobject.customerinfo.CustomerInfoDO
;
...
...
@@ -29,21 +38,58 @@ public class CustomerInfoServiceImpl implements CustomerInfoService {
@Resource
private
CustomerInfoMapper
customerInfoMapper
;
@Resource
private
FileService
fileService
;
@Override
public
Long
createCustomerInfo
(
CustomerInfoSaveReqVO
createReqVO
)
{
// 插入
CustomerInfoDO
customerInfo
=
BeanUtils
.
toBean
(
createReqVO
,
CustomerInfoDO
.
class
);
//校验companyName是否重复
CustomerInfoDO
customerInfoDO
=
customerInfoMapper
.
selectByCompanyName
(
customerInfo
.
getCompanyName
());
if
(
customerInfoDO
!=
null
)
{
throw
exception
(
CUSTOMER_INFO_COMPANY_NAME_DUPLICATE
);
}
String
url
=
uploadMapImageByUrl
(
customerInfo
.
getLocationImage
(),
null
);
customerInfo
.
setLocationImage
(
url
);
customerInfoMapper
.
insert
(
customerInfo
);
// 返回
return
customerInfo
.
getId
();
}
public
String
uploadMapImageByUrl
(
String
imageUrl
,
String
directory
)
{
try
(
CloseableHttpClient
httpClient
=
HttpClients
.
createDefault
())
{
String
fixedUrl
=
imageUrl
.
replace
(
"|"
,
"%7C"
);
HttpGet
request
=
new
HttpGet
(
fixedUrl
);
try
(
CloseableHttpResponse
response
=
httpClient
.
execute
(
request
))
{
HttpEntity
entity
=
response
.
getEntity
();
byte
[]
content
=
EntityUtils
.
toByteArray
(
entity
);
// 文件名:你可以从 URL 中提取或自定义
String
name
=
"map_"
+
System
.
currentTimeMillis
()
+
".jpg"
;
String
type
=
ContentType
.
getOrDefault
(
entity
).
getMimeType
();
// 调用你已有的 createFile 方法
return
fileService
.
createFile
(
content
,
name
,
directory
,
type
);
}
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
"下载地图图片失败:"
+
imageUrl
,
e
);
}
}
@Override
public
void
updateCustomerInfo
(
CustomerInfoSaveReqVO
updateReqVO
)
{
// 校验存在
validateCustomerInfoExists
(
updateReqVO
.
getId
());
// 更新
CustomerInfoDO
updateObj
=
BeanUtils
.
toBean
(
updateReqVO
,
CustomerInfoDO
.
class
);
CustomerInfoDO
customerInfoDO
=
customerInfoMapper
.
selectByCompanyName
(
updateObj
.
getCompanyName
());
if
(
customerInfoDO
!=
null
)
{
throw
exception
(
CUSTOMER_INFO_COMPANY_NAME_DUPLICATE
);
}
if
(
updateObj
.
getLocationImage
()
!=
null
&&
updateObj
.
getLocationImage
().
startsWith
(
"https://apis.map.qq.com/ws/staticmap/v2/"
)){
String
url
=
uploadMapImageByUrl
(
updateObj
.
getLocationImage
(),
null
);
updateObj
.
setLocationImage
(
url
);
}
customerInfoMapper
.
updateById
(
updateObj
);
}
...
...
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/info/InfoService.java
View file @
882f447d
...
...
@@ -51,5 +51,18 @@ public interface InfoService {
* @return 客户拜访记录分页
*/
PageResult
<
InfoDO
>
getInfoPage
(
InfoPageReqVO
pageReqVO
);
/**
* 获得客户拜访记录打印数据 根据ids
*
* @param ids 查询
* @return 客户拜访记录
*/
List
<
InfoPrintVO
>
getInfoByIds
(
List
<
Long
>
ids
);
/**
* 获得客户拜访记录打印数据 根据客户名称
*
* @param companyName 查询
* @return 客户拜访记录
*/
InfoPrintVO
getInfoByCompanyName
(
String
companyName
);
}
\ No newline at end of file
yudao-module-visit/src/main/java/cn/iocoder/yudao/module/visit/service/info/InfoServiceImpl.java
View file @
882f447d
...
...
@@ -2,21 +2,37 @@ package cn.iocoder.yudao.module.visit.service.info;
import
cn.iocoder.yudao.framework.common.pojo.PageResult
;
import
cn.iocoder.yudao.framework.common.util.object.BeanUtils
;
import
cn.iocoder.yudao.module.infra.service.file.FileService
;
import
cn.iocoder.yudao.module.visit.controller.admin.customerinfo.vo.CustomerInfoSaveReqVO
;
import
cn.iocoder.yudao.module.visit.controller.admin.info.vo.InfoPageReqVO
;
import
cn.iocoder.yudao.module.visit.controller.admin.info.vo.InfoPrintVO
;
import
cn.iocoder.yudao.module.visit.controller.admin.info.vo.InfoSaveReqVO
;
import
cn.iocoder.yudao.module.visit.dal.dataobject.customerinfo.CustomerInfoDO
;
import
cn.iocoder.yudao.module.visit.dal.dataobject.info.InfoDO
;
import
cn.iocoder.yudao.module.visit.dal.mysql.customerinfo.CustomerInfoMapper
;
import
cn.iocoder.yudao.module.visit.dal.mysql.info.InfoMapper
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.http.HttpEntity
;
import
org.apache.http.client.methods.CloseableHttpResponse
;
import
org.apache.http.client.methods.HttpGet
;
import
org.apache.http.entity.ContentType
;
import
org.apache.http.impl.client.CloseableHttpClient
;
import
org.apache.http.impl.client.HttpClients
;
import
org.apache.http.util.EntityUtils
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
org.springframework.validation.annotation.Validated
;
import
javax.annotation.Resource
;
import
java.io.IOException
;
import
java.time.LocalDateTime
;
import
java.util.*
;
import
java.util.stream.Collectors
;
import
static
cn
.
iocoder
.
yudao
.
framework
.
common
.
exception
.
util
.
ServiceExceptionUtil
.
exception
;
import
static
cn
.
iocoder
.
yudao
.
module
.
visit
.
enums
.
ErrorCodeConstants
.
INFO_NOT_EXISTS
;
import
static
java
.
nio
.
file
.
Files
.
createFile
;
/**
* 客户拜访记录 Service 实现类
...
...
@@ -33,11 +49,16 @@ public class InfoServiceImpl implements InfoService {
@Resource
private
CustomerInfoMapper
customerInfoMapper
;
@Resource
private
FileService
fileService
;
@Override
@Transactional
(
rollbackFor
=
Exception
.
class
)
public
Long
createInfo
(
InfoSaveReqVO
createReqVO
)
{
// 插入
InfoDO
info
=
BeanUtils
.
toBean
(
createReqVO
,
InfoDO
.
class
);
String
url
=
uploadMapImageByUrl
(
info
.
getLocationImage
(),
null
);
info
.
setLocationImage
(
url
);
int
insert
=
infoMapper
.
insert
(
info
);
if
(
insert
>
0
){
//拜访记录插入成功,根据【公司名称】判断该客户是否记录在案,如果没有,则插入一条客户信息
...
...
@@ -49,6 +70,7 @@ public class InfoServiceImpl implements InfoService {
customerInfoSaveReqVO
.
setContact
(
info
.
getContact
());
customerInfoSaveReqVO
.
setCompanyName
(
info
.
getCompanyName
());
customerInfoSaveReqVO
.
setCustomerType
(
info
.
getCustomerStatus
());
customerInfoSaveReqVO
.
setDepartment
(
info
.
getDepartment
());
customerInfoSaveReqVO
.
setProvinceName
(
info
.
getProvinceName
());
customerInfoSaveReqVO
.
setCityName
(
info
.
getCityName
());
customerInfoSaveReqVO
.
setAreaName
(
info
.
getAreaName
());
...
...
@@ -67,12 +89,36 @@ public class InfoServiceImpl implements InfoService {
return
info
.
getId
();
}
public
String
uploadMapImageByUrl
(
String
imageUrl
,
String
directory
)
{
try
(
CloseableHttpClient
httpClient
=
HttpClients
.
createDefault
())
{
String
fixedUrl
=
imageUrl
.
replace
(
"|"
,
"%7C"
);
HttpGet
request
=
new
HttpGet
(
fixedUrl
);
try
(
CloseableHttpResponse
response
=
httpClient
.
execute
(
request
))
{
HttpEntity
entity
=
response
.
getEntity
();
byte
[]
content
=
EntityUtils
.
toByteArray
(
entity
);
// 文件名:你可以从 URL 中提取或自定义
String
name
=
"map_"
+
System
.
currentTimeMillis
()
+
".jpg"
;
String
type
=
ContentType
.
getOrDefault
(
entity
).
getMimeType
();
// 调用你已有的 createFile 方法
return
fileService
.
createFile
(
content
,
name
,
directory
,
type
);
}
}
catch
(
IOException
e
)
{
throw
new
RuntimeException
(
"下载地图图片失败:"
+
imageUrl
,
e
);
}
}
@Override
public
void
updateInfo
(
InfoSaveReqVO
updateReqVO
)
{
// 校验存在
validateInfoExists
(
updateReqVO
.
getId
());
// 更新
InfoDO
updateObj
=
BeanUtils
.
toBean
(
updateReqVO
,
InfoDO
.
class
);
if
(
updateObj
.
getLocationImage
()
!=
null
&&
updateObj
.
getLocationImage
().
startsWith
(
"https://apis.map.qq.com/ws/staticmap/v2/"
)){
String
url
=
uploadMapImageByUrl
(
updateObj
.
getLocationImage
(),
null
);
updateObj
.
setLocationImage
(
url
);
}
infoMapper
.
updateById
(
updateObj
);
}
...
...
@@ -100,4 +146,92 @@ public class InfoServiceImpl implements InfoService {
return
infoMapper
.
selectPage
(
pageReqVO
);
}
@Override
public
List
<
InfoPrintVO
>
getInfoByIds
(
List
<
Long
>
ids
)
{
List
<
InfoPrintVO
>
vos
=
new
ArrayList
<>();
//先获取客户拜访记录
List
<
InfoDO
>
info
=
infoMapper
.
selectByIds
(
ids
);
if
(
info
!=
null
&&
!
info
.
isEmpty
()){
//获取客户拜访记录成功,获取客户信息
info
.
forEach
(
infoDO
->
{
InfoPrintVO
infoPrintVO
=
new
InfoPrintVO
();
infoPrintVO
.
setServiceContent
(
Collections
.
singletonList
(
infoDO
.
getServiceContent
()));
//本次服务内容
//服务图片(最多四张)
String
imagesStr
=
infoDO
.
getServiceImages
();
if
(
StringUtils
.
isNotBlank
(
imagesStr
))
{
String
limitedImagesStr
=
Arrays
.
stream
(
imagesStr
.
split
(
","
))
.
limit
(
4
)
.
collect
(
Collectors
.
joining
(
","
));
infoPrintVO
.
setServiceImages
(
limitedImagesStr
);
}
else
{
infoPrintVO
.
setServiceImages
(
""
);
}
infoPrintVO
.
setVisitProductNames
(
infoDO
.
getVisitProductNames
());
//本次拜访品种
infoPrintVO
.
setCompanyName
(
infoDO
.
getCompanyName
());
//公司名称
infoPrintVO
.
setDepartment
(
infoDO
.
getDepartment
());
//部门
infoPrintVO
.
setContact
(
infoDO
.
getContact
());
//联系方式
infoPrintVO
.
setSalesman
(
infoDO
.
getCreator
());
//拜访记录的创建者就是该记录的业务员
infoPrintVO
.
setLocationImage
(
infoDO
.
getLocationImage
());
infoPrintVO
.
setVisitDate
(
Collections
.
singletonList
(
infoDO
.
getVisitDate
()));
//拜访次数按拜访id打印都是1
infoPrintVO
.
setServiceCount
(
1
);
vos
.
add
(
infoPrintVO
);
});
}
return
vos
;
}
@Override
public
InfoPrintVO
getInfoByCompanyName
(
String
companyName
)
{
InfoPrintVO
infoPrintVO
=
new
InfoPrintVO
();
//服务内容(最近四次)
List
<
String
>
serviceContent
=
new
ArrayList
<>();
//服务图片(最多四张)
List
<
String
>
serviceImages
=
new
ArrayList
<>();
//拜访时间记录
List
<
LocalDateTime
>
visitDateList
=
new
ArrayList
<>();
//现根据客户名称获取历史拜访记录
List
<
InfoDO
>
info
=
infoMapper
.
selectByCompanyName
(
companyName
);
if
(
info
!=
null
&&
!
info
.
isEmpty
()){
//取最近四次
info
=
info
.
stream
().
sorted
(
Comparator
.
comparing
(
InfoDO:
:
getVisitDate
).
reversed
()).
limit
(
4
).
collect
(
Collectors
.
toList
());
//取最多4张服务图片
int
maxImages
=
4
;
for
(
InfoDO
infoDO
:
info
)
{
if
(
serviceImages
.
size
()
>=
maxImages
)
break
;
String
imagesStr
=
infoDO
.
getServiceImages
();
if
(
StringUtils
.
isNotBlank
(
imagesStr
))
{
List
<
String
>
imageList
=
Arrays
.
asList
(
imagesStr
.
split
(
","
));
int
remaining
=
maxImages
-
serviceImages
.
size
();
serviceImages
.
addAll
(
imageList
.
stream
().
limit
(
remaining
).
collect
(
Collectors
.
toList
()));
}
}
//获取客户拜访记录成功,获取客户信息
info
.
forEach
(
infoDO
->
{
//服务内容记录
serviceContent
.
add
(
infoDO
.
getServiceContent
());
//拜访时间记录
visitDateList
.
add
(
infoDO
.
getVisitDate
());
});
//下面获取客户信息
CustomerInfoDO
customerInfoDO
=
customerInfoMapper
.
selectByCompanyName
(
companyName
);
infoPrintVO
.
setVisitProductNames
(
customerInfoDO
.
getProductNames
());
//本次拜访品种
infoPrintVO
.
setCompanyName
(
customerInfoDO
.
getCompanyName
());
//公司名称
infoPrintVO
.
setSalesman
(
customerInfoDO
.
getCreator
());
//拜访记录的创建者就是该记录的业务员
infoPrintVO
.
setLocationImage
(
customerInfoDO
.
getLocationImage
());
//公司定位图
infoPrintVO
.
setContact
(
customerInfoDO
.
getContact
());
infoPrintVO
.
setDepartment
(
customerInfoDO
.
getDepartment
());
//添加多次的信息
infoPrintVO
.
setServiceContent
(
serviceContent
);
infoPrintVO
.
setVisitDate
(
visitDateList
);
infoPrintVO
.
setServiceImages
(
String
.
join
(
","
,
serviceImages
));
//拜访次数
infoPrintVO
.
setServiceCount
(
info
.
size
());
}
else
{
return
null
;
}
return
infoPrintVO
;
}
}
\ No newline at end of file
yudao-ui/yudao-ui-admin-vue3/package-lock.json
View file @
882f447d
...
...
@@ -15,7 +15,7 @@
"@iconify/iconify"
:
"^3.1.1"
,
"@microsoft/fetch-event-source"
:
"^2.0.1"
,
"@videojs-player/vue"
:
"^1.0.0"
,
"@vueuse/core"
:
"^10.
9.0
"
,
"@vueuse/core"
:
"^10.
11.1
"
,
"@wangeditor/editor"
:
"^5.1.23"
,
"@wangeditor/editor-for-vue"
:
"^5.1.10"
,
"@zxcvbn-ts/core"
:
"^3.0.4"
,
...
...
@@ -59,6 +59,7 @@
"vue-i18n"
:
"9.10.2"
,
"vue-router"
:
"4.4.5"
,
"vue-types"
:
"^5.1.1"
,
"vue3-print-nb"
:
"^0.1.4"
,
"vue3-signature"
:
"^0.2.4"
,
"vuedraggable"
:
"^4.1.0"
,
"web-storage-cache"
:
"^1.1.1"
,
...
...
@@ -6908,7 +6909,6 @@
"version"
:
"10.11.1"
,
"resolved"
:
"https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz"
,
"integrity"
:
"sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww=="
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@types/web-bluetooth"
:
"^0.0.20"
,
"@vueuse/metadata"
:
"10.11.1"
,
...
...
@@ -17077,6 +17077,14 @@
"integrity"
:
"sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg=="
,
"license"
:
"MIT"
},
"node_modules/vue3-print-nb"
:
{
"version"
:
"0.1.4"
,
"resolved"
:
"https://registry.npmjs.org/vue3-print-nb/-/vue3-print-nb-0.1.4.tgz"
,
"integrity"
:
"sha512-LExI7viEzplR6ZKQ2b+V4U0cwGYbVD4fut/XHvk3UPGlT5CcvIGs6VlwGp107aKgk6P8Pgx4rco3Rehv2lti3A=="
,
"dependencies"
:
{
"vue"
:
"^3.0.5"
}
},
"node_modules/vue3-signature"
:
{
"version"
:
"0.2.4"
,
"resolved"
:
"https://registry.npmjs.org/vue3-signature/-/vue3-signature-0.2.4.tgz"
,
...
...
yudao-ui/yudao-ui-admin-vue3/package.json
View file @
882f447d
...
...
@@ -31,7 +31,7 @@
"@iconify/iconify"
:
"^3.1.1"
,
"@microsoft/fetch-event-source"
:
"^2.0.1"
,
"@videojs-player/vue"
:
"^1.0.0"
,
"@vueuse/core"
:
"^10.
9.0
"
,
"@vueuse/core"
:
"^10.
11.1
"
,
"@wangeditor/editor"
:
"^5.1.23"
,
"@wangeditor/editor-for-vue"
:
"^5.1.10"
,
"@zxcvbn-ts/core"
:
"^3.0.4"
,
...
...
@@ -75,6 +75,7 @@
"vue-i18n"
:
"9.10.2"
,
"vue-router"
:
"4.4.5"
,
"vue-types"
:
"^5.1.1"
,
"vue3-print-nb"
:
"^0.1.4"
,
"vue3-signature"
:
"^0.2.4"
,
"vuedraggable"
:
"^4.1.0"
,
"web-storage-cache"
:
"^1.1.1"
,
...
...
yudao-ui/yudao-ui-admin-vue3/src/api/visit/info/index.ts
View file @
882f447d
...
...
@@ -21,8 +21,40 @@ export interface InfoVO {
serviceContent
:
string
// 服务内容
customerFeedback
:
string
// 客户反馈
serviceImages
:
string
// 服务记录图片URL列表(JSON数组)
department
:
string
// 客户部门
}
export
interface
InfoPrintVO
{
/** 联系方式 */
contact
:
string
;
/** 客户公司名称 */
companyName
:
string
;
/** 客户部门 */
department
?:
string
;
/** 定位静态图(URL) */
locationImage
:
string
;
/** 最近四次拜访日期 */
visitDate
:
string
[];
// ISO 格式字符串,或可用 Date[] 视后端返回而定
/** 拜访品种 */
visitProductNames
?:
string
;
/** 最近四次服务内容 */
serviceContent
:
string
[];
/** 服务记录图片URL列表(逗号分隔的字符串) */
serviceImages
:
string
;
/** 业务员 */
salesman
:
string
;
/** 服务数量 */
serviceCount
:
number
;
}
// 客户拜访记录 API
export
const
InfoApi
=
{
// 查询客户拜访记录分页
...
...
@@ -35,6 +67,17 @@ export const InfoApi = {
return
await
request
.
get
({
url
:
`/visit/info/get?id=`
+
id
})
},
//查询打印客户拜访记录
getPrintListByIds
:
async
(
ids
:
string
)
=>
{
return
await
request
.
get
({
url
:
`/visit/info/getPrintListByIds`
,
params
:
{
ids
}
})
},
//查询打印客户拜访记录
getPrintListByCompanyName
:
async
(
companyName
:
string
)
=>
{
return
await
request
.
get
({
url
:
`/visit/info/getPrintListByCompanyName`
,
params
:
{
companyName
}
})
},
// 新增客户拜访记录
createInfo
:
async
(
data
:
InfoVO
)
=>
{
return
await
request
.
post
({
url
:
`/visit/info/create`
,
data
})
...
...
yudao-ui/yudao-ui-admin-vue3/src/views/system/user/UserForm.vue
View file @
882f447d
...
...
@@ -96,7 +96,6 @@
</Dialog>
</template>
<
script
lang=
"ts"
setup
>
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'
@/utils/dict
'
import
{
CommonStatusEnum
}
from
'
@/utils/constants
'
import
{
defaultProps
,
handleTree
}
from
'
@/utils/tree
'
import
*
as
DeptApi
from
'
@/api/system/dept
'
...
...
yudao-ui/yudao-ui-admin-vue3/src/views/visit/customerinfo/CustomerInfoForm.vue
View file @
882f447d
...
...
@@ -11,7 +11,7 @@
<el-input
v-model=
"formData.customerName"
placeholder=
"请输入客户姓名"
/>
</el-form-item>
<el-form-item
label=
"联系方式"
prop=
"contact"
>
<el-input
v-model=
"formData.contact"
placeholder=
"请输入联系方式"
/>
<el-input
v-model=
"formData.contact"
maxlength=
"11"
placeholder=
"请输入联系方式"
/>
</el-form-item>
<el-form-item
label=
"公司名称"
prop=
"companyName"
>
<el-input
v-model=
"formData.companyName"
placeholder=
"请输入公司名称"
/>
...
...
@@ -153,9 +153,9 @@ const formData = ref<CustomerFormData>({
})
const
formRules
=
reactive
({
customerName
:
[{
required
:
true
,
message
:
'
客户姓名不能为空
'
,
trigger
:
'
blur
'
}],
contact
:
[{
required
:
true
,
message
:
'
联系方式不能为空
'
,
trigger
:
'
blur
'
}],
companyName
:
[{
required
:
true
,
message
:
'
公司名称不能为空
'
,
trigger
:
'
blur
'
}],
customerType
:
[{
required
:
true
,
message
:
'
性质等级不能为空
'
,
trigger
:
'
change
'
}],
department
:
[{
required
:
true
,
message
:
'
客户部门不能为空
'
,
trigger
:
'
change
'
}],
provinceName
:
[{
required
:
true
,
message
:
'
省名称不能为空
'
,
trigger
:
'
blur
'
}],
cityName
:
[{
required
:
true
,
message
:
'
市名称不能为空
'
,
trigger
:
'
blur
'
}],
areaName
:
[{
required
:
true
,
message
:
'
区名称不能为空
'
,
trigger
:
'
blur
'
}],
...
...
@@ -194,7 +194,7 @@ const handleLocation = ({ lat, lng, address }) => {
formData
.
value
.
longitude
=
lng
formData
.
value
.
locationText
=
address
//静态图
const
mapKey
=
'
KHXBZ-OVYYZ-N4NXF-7JCZ2-PR4FT-RYF4E
'
const
mapKey
=
'
2OZBZ-WUCE7-SLKXP-HJVOW-3P6RF-WVB7H
'
const
mapUrl
=
`https://apis.map.qq.com/ws/staticmap/v2/?center=
${
lat
}
,
${
lng
}
&zoom=13&size=600*300&maptype=roadmap&markers=size:large|color:0xFFCCFF|label:k|
${
lat
}
,
${
lng
}
&key=
${
mapKey
}
`
formData
.
value
.
locationImage
=
mapUrl
...
...
yudao-ui/yudao-ui-admin-vue3/src/views/visit/customerinfo/index.vue
View file @
882f447d
...
...
@@ -87,17 +87,18 @@
<!-- 列表 -->
<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"
@
selection-change=
"handleSelectionChange"
>
<el-table-column
type=
"selection"
width=
"55"
/>
<el-table-column
label=
"编号"
align=
"center"
prop=
"id"
/>
<el-table-column
label=
"客户姓名"
align=
"center"
prop=
"customerName"
/>
<el-table-column
label=
"联系方式"
align=
"center"
prop=
"contact"
/>
<el-table-column
label=
"公司名称"
align=
"center"
prop=
"companyName"
/>
<el-table-column
label=
"客户姓名"
align=
"center"
prop=
"customerName"
min-width=
"100px"
/>
<el-table-column
label=
"联系方式"
align=
"center"
prop=
"contact"
min-width=
"120px"
/>
<el-table-column
label=
"公司名称"
align=
"center"
prop=
"companyName"
min-width=
"160px"
/>
<el-table-column
label=
"性质等级"
align=
"center"
prop=
"customerType"
>
<template
#default
="
scope
"
>
<dict-tag
:type=
"DICT_TYPE.CUSTOMER_TYPE"
:value=
"scope.row.customerType"
/>
</
template
>
</el-table-column>
<el-table-column
label=
"所在地区"
align=
"center"
prop=
"regionFullName"
/>
<el-table-column
label=
"所在地区"
align=
"center"
prop=
"regionFullName"
min-width=
"160px"
/>
<el-table-column
label=
"创建时间"
align=
"center"
...
...
@@ -105,13 +106,27 @@
:formatter=
"dateFormatter"
width=
"180px"
/>
<el-table-column
label=
"产品信息"
prop=
"productIds"
>
<el-table-column
label=
"产品信息"
prop=
"productIds"
min-width=
"100px"
>
<
template
#default
="
scope
"
>
<ProductList
:ids=
scope.row.productIds
/>
</
template
>
</el-table-column>
<el-table-column
label=
"操作"
align=
"center"
min-width=
"
120px
"
>
<el-table-column
label=
"操作"
align=
"center"
min-width=
"
320px"
fixed=
"right
"
>
<
template
#default
="
scope
"
>
<el-button
link
type=
"primary"
@
click=
"visitInfo(scope.row.companyName)"
>
拜访记录
</el-button>
<el-button
link
type=
"primary"
@
click=
"handlePrint(scope.row.companyName)"
>
打印拜访记录
</el-button>
<el-button
link
type=
"primary"
...
...
@@ -142,6 +157,8 @@
<!-- 表单弹窗:添加/修改 -->
<CustomerInfoForm
ref=
"formRef"
@
success=
"getList"
/>
<!-- 打印弹窗 -->
<VisitPrint
ref=
"visitPrintRef"
:data=
"selectedData"
/>
</template>
<
script
setup
lang=
"ts"
>
...
...
@@ -151,10 +168,13 @@ import download from '@/utils/download'
import
{
CustomerInfoApi
,
CustomerInfoVO
}
from
'
@/api/visit/customerinfo
'
import
CustomerInfoForm
from
'
./CustomerInfoForm.vue
'
import
ProductList
from
"
@/views/visit/util/ProductList.vue
"
;
import
{
useRouter
}
from
'
vue-router
'
import
{
InfoApi
,
InfoPrintVO
}
from
"
@/api/visit/info
"
;
/** 客户信息 列表 */
defineOptions
({
name
:
'
CustomerInfo
'
})
const
router
=
useRouter
()
const
message
=
useMessage
()
// 消息弹窗
const
{
t
}
=
useI18n
()
// 国际化
...
...
@@ -173,6 +193,30 @@ const queryParams = reactive({
})
const
queryFormRef
=
ref
()
// 搜索的表单
const
exportLoading
=
ref
(
false
)
// 导出的加载中
const
multipleSelection
=
ref
([])
//存储多选内容
const
visitPrintRef
=
ref
()
// 模拟获取详情数据的方法(你应该调 InfoApi.getInfoListByIds 或类似接口)
const
selectedData
=
ref
<
InfoPrintVO
[]
>
([])
const
handlePrint
=
async
(
companyName
:
string
)
=>
{
selectedData
.
value
=
await
InfoApi
.
getPrintListByCompanyName
(
companyName
)
//判断是否有数据
if
(
!
selectedData
.
value
)
{
message
.
warning
(
'
该客户暂无拜访记录
'
)
return
}
console
.
log
(
selectedData
.
value
)
await
nextTick
()
visitPrintRef
.
value
?.
print
()
}
/** 多选操作**/
const
handleSelectionChange
=
(
val
)
=>
{
multipleSelection
.
value
=
val
.
map
(
item
=>
item
.
id
)
//console.log(val,multipleSelection)
}
/** 查询列表 */
const
getList
=
async
()
=>
{
...
...
@@ -191,7 +235,9 @@ const handleQuery = () => {
queryParams
.
pageNo
=
1
getList
()
}
const
visitInfo
=
(
data
)
=>
{
router
.
push
({
path
:
'
/visit/info
'
,
query
:
{
companyName
:
data
}
})
}
/** 重置按钮操作 */
const
resetQuery
=
()
=>
{
queryFormRef
.
value
.
resetFields
()
...
...
yudao-ui/yudao-ui-admin-vue3/src/views/visit/info/InfoForm.vue
View file @
882f447d
...
...
@@ -11,7 +11,7 @@
<el-input
v-model=
"formData.customerName"
placeholder=
"请输入拜访人姓名"
/>
</el-form-item>
<el-form-item
label=
"联系方式"
prop=
"contact"
>
<el-input
v-model=
"formData.contact"
placeholder=
"请输入联系方式"
/>
<el-input
v-model=
"formData.contact"
maxlength=
"11"
placeholder=
"请输入联系方式"
/>
</el-form-item>
<el-form-item
label=
"公司名称"
prop=
"companyName"
>
<el-autocomplete
...
...
@@ -62,6 +62,16 @@
/>
</el-select>
</el-form-item>
<el-form-item
label=
"客户部门"
prop=
"department"
>
<el-select
v-model=
"formData.department"
placeholder=
"请选择客户部门"
>
<el-option
v-for=
"dict in getStrDictOptions(DICT_TYPE.CUSTOMER_DEPT)"
:key=
"dict.value"
:label=
"dict.label"
:value=
"dict.value"
/>
</el-select>
</el-form-item>
<el-form-item
label=
"拜访品种"
prop=
"visitProductNames"
>
<el-input
v-model=
"formData.visitProductNames"
...
...
@@ -125,7 +135,7 @@
<productTable
ref=
"productListRef"
@
updateProduct=
"handleUpdateProduct"
:ids=
"formData.visitProductIds"
/>
</template>
<
script
setup
lang=
"ts"
>
import
{
getIntDictOptions
,
DICT_TYPE
}
from
'
@/utils/dict
'
import
{
getIntDictOptions
,
DICT_TYPE
,
getStrDictOptions
}
from
'
@/utils/dict
'
import
{
InfoApi
,
InfoVO
}
from
'
@/api/visit/info
'
import
{
CustomerInfoApi
}
from
'
@/api/visit/customerinfo
'
import
productTable
from
"
@/views/visit/customerinfo/productTable.vue
"
;
...
...
@@ -159,6 +169,7 @@ const formData = ref({
locationImage
:
undefined
,
visitDate
:
Date
.
now
(),
customerStatus
:
undefined
,
department
:
undefined
,
visitProductIds
:
undefined
,
visitProductNames
:
undefined
,
visitMethod
:
undefined
,
...
...
@@ -169,7 +180,6 @@ const formData = ref({
})
const
formRules
=
reactive
({
customerName
:
[{
required
:
true
,
message
:
'
拜访人姓名不能为空
'
,
trigger
:
'
blur
'
}],
contact
:
[{
required
:
true
,
message
:
'
联系方式不能为空
'
,
trigger
:
'
blur
'
}],
companyName
:
[{
required
:
true
,
message
:
'
客户公司名称不能为空
'
,
trigger
:
'
blur
'
}],
provinceName
:
[{
required
:
true
,
message
:
'
省名称不能为空
'
,
trigger
:
'
blur
'
}],
cityName
:
[{
required
:
true
,
message
:
'
市名称不能为空
'
,
trigger
:
'
blur
'
}],
...
...
@@ -181,6 +191,7 @@ const formRules = reactive({
locationImage
:
[{
required
:
true
,
message
:
'
定位静态图URL不能为空
'
,
trigger
:
'
blur
'
}],
visitDate
:
[{
required
:
true
,
message
:
'
拜访日期不能为空
'
,
trigger
:
'
blur
'
}],
customerStatus
:
[{
required
:
true
,
message
:
'
性质等级不能为空
'
,
trigger
:
'
blur
'
}],
department
:
[{
required
:
true
,
message
:
'
客户部门不能为空
'
,
trigger
:
'
blur
'
}],
serviceContent
:
[{
required
:
true
,
message
:
'
请输入服务内容
'
,
trigger
:
'
blur
'
},
{
max
:
500
,
message
:
'
最多输入 500 个字符
'
,
trigger
:
'
blur
'
}],
customerFeedback
:
[{
required
:
true
,
message
:
'
请输入客户反馈
'
,
trigger
:
'
blur
'
},
{
max
:
500
,
message
:
'
最多输入 500 个字符
'
,
trigger
:
'
blur
'
}],
})
...
...
@@ -282,7 +293,7 @@ const handleLocation = ({ lat, lng, address }) => {
formData
.
value
.
longitude
=
lng
formData
.
value
.
locationText
=
address
//静态图
const
mapKey
=
'
KHXBZ-OVYYZ-N4NXF-7JCZ2-PR4FT-RYF4E
'
const
mapKey
=
'
2OZBZ-WUCE7-SLKXP-HJVOW-3P6RF-WVB7H
'
const
mapUrl
=
`https://apis.map.qq.com/ws/staticmap/v2/?center=
${
lat
}
,
${
lng
}
&zoom=13&size=600*300&maptype=roadmap&markers=size:large|color:0xFFCCFF|label:k|
${
lat
}
,
${
lng
}
&key=
${
mapKey
}
`
formData
.
value
.
locationImage
=
mapUrl
...
...
@@ -386,6 +397,7 @@ const resetForm = () => {
locationImage
:
undefined
,
visitDate
:
Date
.
now
(),
customerStatus
:
undefined
,
department
:
undefined
,
visitProductIds
:
undefined
,
visitProductNames
:
undefined
,
visitMethod
:
undefined
,
...
...
yudao-ui/yudao-ui-admin-vue3/src/views/visit/info/index.vue
View file @
882f447d
...
...
@@ -126,18 +126,27 @@
>
<Icon
icon=
"ep:download"
class=
"mr-5px"
/>
导出
</el-button>
<el-button
type=
"success"
plain
@
click=
"handlePrint"
:loading=
"exportLoading"
>
<Icon
icon=
"ep:printer"
class=
"mr-5px"
/>
批量打印
</el-button>
</el-form-item>
</el-form>
</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"
@
selection-change=
"handleSelectionChange"
>
<el-table-column
type=
"selection"
width=
"55"
/>
<el-table-column
label=
"主键ID"
align=
"center"
prop=
"id"
/>
<el-table-column
label=
"拜访人姓名"
align=
"center"
prop=
"customerName"
width=
"100px"
/>
<el-table-column
label=
"联系方式"
align=
"center"
prop=
"contact"
width=
"120px"
/>
<el-table-column
label=
"客户公司名称"
align=
"center"
prop=
"companyName"
width=
"160px"
/>
<el-table-column
label=
"所在
省市
区"
align=
"center"
prop=
"regionFullName"
width=
"160px"
/>
<el-table-column
label=
"所在
地
区"
align=
"center"
prop=
"regionFullName"
width=
"160px"
/>
<el-table-column
label=
"拜访日期"
align=
"center"
...
...
@@ -204,15 +213,34 @@
<!-- 表单弹窗:添加/修改 -->
<InfoForm
ref=
"formRef"
@
success=
"getList"
/>
<!-- 打印弹窗 -->
<VisitPrint
ref=
"visitPrintRef"
:data=
"selectedData"
/>
</template>
<
script
setup
lang=
"ts"
>
import
{
getIntDictOptions
,
DICT_TYPE
}
from
'
@/utils/dict
'
import
{
dateFormatter
}
from
'
@/utils/formatTime
'
import
{
DICT_TYPE
,
getIntDictOptions
}
from
'
@/utils/dict
'
import
{
dateFormatter
}
from
'
@/utils/formatTime
'
import
download
from
'
@/utils/download
'
import
{
InfoApi
,
InfoVO
}
from
'
@/api/visit/info
'
import
{
InfoApi
,
InfoPrintVO
,
InfoVO
}
from
'
@/api/visit/info
'
import
InfoForm
from
'
./InfoForm.vue
'
import
ProductList
from
"
@/views/visit/util/ProductList.vue
"
;
import
{
useRoute
}
from
'
vue-router
'
const
visitPrintRef
=
ref
()
// 模拟获取详情数据的方法(你应该调 InfoApi.getInfoListByIds 或类似接口)
const
selectedData
=
ref
<
InfoPrintVO
[]
>
([])
const
handlePrint
=
async
()
=>
{
if
(
multipleSelection
.
value
.
length
===
0
)
{
return
message
.
warning
(
'
请先选择需要打印的记录
'
)
}
selectedData
.
value
=
await
InfoApi
.
getPrintListByIds
(
multipleSelection
.
value
.
join
(
'
,
'
))
console
.
log
(
selectedData
.
value
)
await
nextTick
()
visitPrintRef
.
value
?.
print
()
}
const
route
=
useRoute
()
/** 客户拜访记录 列表 */
defineOptions
({
name
:
'
Info
'
})
...
...
@@ -238,7 +266,13 @@ const queryParams = reactive({
})
const
queryFormRef
=
ref
()
// 搜索的表单
const
exportLoading
=
ref
(
false
)
// 导出的加载中
const
multipleSelection
=
ref
([])
//存储多选内容
/** 多选操作**/
const
handleSelectionChange
=
(
val
)
=>
{
multipleSelection
.
value
=
val
.
map
(
item
=>
item
.
id
)
//console.log(val,multipleSelection)
}
/** 查询列表 */
const
getList
=
async
()
=>
{
loading
.
value
=
true
...
...
@@ -299,6 +333,10 @@ const handleExport = async () => {
/** 初始化 **/
onMounted
(()
=>
{
const
companyName
=
route
.
query
.
companyName
if
(
companyName
){
queryParams
.
companyName
=
companyName
}
getList
()
})
</
script
>
yudao-ui/yudao-ui-admin-vue3/src/views/visit/product/index.vue
View file @
882f447d
...
...
@@ -182,7 +182,7 @@ const multipleSelection = ref([]) //存储多选内容
/** 多选操作**/
const
handleSelectionChange
=
(
val
)
=>
{
multipleSelection
.
value
=
val
.
map
(
item
=>
item
.
id
)
console
.
log
(
val
,
multipleSelection
)
//
console.log(val,multipleSelection)
}
/** 查询列表 */
const
getList
=
async
()
=>
{
...
...
yudao-ui/yudao-ui-admin-vue3/src/views/visit/util/MapPicker.vue
View file @
882f447d
...
...
@@ -26,7 +26,7 @@ import { ref, onMounted, onUnmounted } from 'vue'
const
visible
=
ref
(
false
)
// ✅ 腾讯地图 locationPicker 地址,替换成你的 key & 应用名
const
pickerUrl
=
`https://apis.map.qq.com/tools/locpicker?search=1&type=1&key=
KHXBZ-OVYYZ-N4NXF-7JCZ2-PR4FT-RYF4E
&referer=VISITKEY`
const
pickerUrl
=
`https://apis.map.qq.com/tools/locpicker?search=1&type=1&key=
2OZBZ-WUCE7-SLKXP-HJVOW-3P6RF-WVB7H
&referer=VISITKEY`
// ✅ 定义暴露 open() 方法,父组件通过 ref 调用
const
open
=
()
=>
{
...
...
yudao-ui/yudao-ui-admin-vue3/src/views/visit/util/VisitPrint.vue
0 → 100644
View file @
882f447d
<
template
>
<div
ref=
"printArea"
style=
"padding: 20px; font-size: 14px;"
v-show=
"visible"
>
<div
v-for=
"(item, index) in data"
:key=
"index"
class=
"print-page"
>
<h2
style=
"text-align: center; margin-bottom: 16px;"
>
渠道服务记录
</h2>
<div
style=
"display: flex;"
>
<!-- 左侧 1/3 -->
<div
style=
"width: 33%;"
>
<img
crossorigin=
"anonymous"
:src=
"item.locationImage"
style=
"width: 100%; height: 120px;"
alt=
""
/>
<p>
业务员:
{{
item
.
salesman
}}
</p>
<p>
服务数量:
{{
item
.
serviceCount
}}
</p>
<h4>
拜访时间记录
</h4>
<ul>
<li
v-for=
"(time, i) in item.visitDate"
:key=
"i"
>
{{
formatDate
(
time
)
}}
</li>
</ul>
<h4>
主要服务内容
</h4>
<ul>
<li
v-for=
"(content, i) in item.serviceContent"
:key=
"i"
>
内容
{{
i
+
1
}}
:
{{
content
}}
</li>
</ul>
</div>
<!-- 右侧 2/3 -->
<div
style=
"width: 67%; padding-left: 20px;"
>
<p>
客户名称:
{{
item
.
companyName
}}
</p>
<p>
客户部门:
{{
getDictLabel
(
DICT_TYPE
.
CUSTOMER_DEPT
,
item
.
department
)
}}
</p>
<p>
推广产品:
{{
item
.
visitProductNames
}}
</p>
<div
style=
"border: 1px solid #ccc; padding: 10px; margin-bottom: 10px;"
>
拜访签到实景图
</div>
<div
style=
"display: flex; flex-wrap: wrap; gap: 10px;"
>
<img
crossorigin=
"anonymous"
v-for=
"(url, i) in getSafeImages(item.serviceImages)"
:key=
"i"
:src=
"url"
style=
"width: calc(50% - 5px); height: 100px; object-fit: cover; border: 1px solid #ccc;"
/>
</div>
</div>
</div>
<div
style=
"page-break-after: always;"
></div>
</div>
</div>
</
template
>
<
script
setup
lang=
"ts"
>
import
{
nextTick
,
ref
}
from
'
vue
'
import
{
getDictLabel
,
DICT_TYPE
}
from
'
@/utils/dict
'
const
props
=
defineProps
<
{
data
:
any
[]
}
>
()
const
printArea
=
ref
<
HTMLElement
|
null
>
(
null
)
const
visible
=
ref
(
false
)
const
formatDate
=
(
date
:
string
|
Date
)
=>
{
const
d
=
new
Date
(
date
)
return
d
.
toLocaleString
()
}
const
getSafeImages
=
(
val
?:
string
)
=>
{
if
(
!
val
)
return
[]
return
val
.
split
(
'
,
'
).
slice
(
0
,
4
)
}
const
print
=
async
()
=>
{
const
printWindow
=
window
.
open
(
''
,
'
_blank
'
)
if
(
!
printWindow
)
return
let
html
=
''
for
(
const
item
of
props
.
data
)
{
html
+=
`
<div class="print-page">
<h2>渠道服务记录</h2>
<div style="display: flex;">
<div style="width: 33%;">
<img src="
${
item
.
locationImage
}
" crossorigin="anonymous" style="width: 100%; height: 120px;" />
<p>业务员:
${
item
.
salesman
||
''
}
</p>
<p>服务数量:
${
item
.
serviceCount
||
''
}
</p>
<h4>拜访时间记录</h4>
<ul>
${(
item
.
visitDate
||
[]).
map
((
t
:
string
)
=>
`<li>
${
formatDate
(
t
)}
</li>`
).
join
(
''
)}
</ul>
<h4>主要服务内容</h4>
<ul>
${(
item
.
serviceContent
||
[]).
map
((
c
:
string
,
i
:
number
)
=>
`<li>内容
${
i
+
1
}
:
${
c
}
</li>`
).
join
(
''
)}
</ul>
</div>
<div style="width: 67%; padding-left: 20px;">
<p>客户名称:
${
item
.
companyName
||
''
}
</p>
<p>客户部门:
${
getDictLabel
(
DICT_TYPE
.
CUSTOMER_DEPT
,
item
.
department
)
||
''
}
</p>
<p>推广产品:
${
item
.
visitProductNames
||
''
}
</p>
<div style="border: 1px solid #ccc; padding: 10px; margin-bottom: 10px;">拜访签到实景图</div>
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
${(
item
.
serviceImages
||
''
)
.
split
(
'
,
'
)
.
slice
(
0
,
4
)
.
map
(
url
=>
`<img src="
${
url
}
" crossorigin="anonymous" style="width: calc(50% - 5px); height: 100px; object-fit: cover; border: 1px solid #ccc;" />`
)
.
join
(
''
)}
</div>
</div>
</div>
<div style="page-break-after: always;"></div>
</div>
`
}
printWindow
.
document
.
write
(
`
<html>
<head>
<title>打印</title>
<meta charset="UTF-8">
<style>
body, html {
margin: 0;
padding: 0;
width: 210mm;
height: auto;
font-size: 14px;
-webkit-print-color-adjust: exact;
}
.print-page {
width: 210mm;
height: 297mm;
padding: 20mm;
box-sizing: border-box;
page-break-after: always;
overflow: hidden;
}
h2 {
text-align: center;
margin-bottom: 16px;
}
img {
max-width: 100%;
object-fit: cover;
}
</style>
</head>
<body>
${
html
}
</body>
</html>
`
)
printWindow
.
document
.
close
()
printWindow
.
focus
()
printWindow
.
print
()
printWindow
.
close
()
}
defineExpose
({
print
})
</
script
>
<
style
scoped
>
/* 普通预览样式 */
#printWrapper
{
width
:
210mm
;
margin
:
0
auto
;
font-size
:
14px
;
}
.print-page
{
width
:
210mm
;
height
:
297mm
;
padding
:
20mm
;
box-sizing
:
border-box
;
page-break-after
:
always
;
overflow
:
hidden
;
position
:
relative
;
display
:
flex
;
flex-direction
:
column
;
justify-content
:
space-between
;
border
:
1px
dashed
transparent
;
}
.print-page
h2
{
text-align
:
center
;
margin-bottom
:
16px
;
}
.print-page
img
{
max-width
:
100%
;
object-fit
:
cover
;
}
.print-page
.images-wrapper
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
10px
;
}
.print-page
.images-wrapper
img
{
width
:
calc
(
50%
-
5px
);
height
:
100px
;
border
:
1px
solid
#ccc
;
}
/* 打印样式 */
@media
print
{
body
,
html
{
margin
:
0
;
padding
:
0
;
width
:
210mm
;
height
:
auto
;
-webkit-print-color-adjust
:
exact
;
}
#printWrapper
{
width
:
210mm
;
margin
:
0
auto
;
}
.print-page
{
width
:
210mm
;
height
:
297mm
;
page-break-after
:
always
;
overflow
:
hidden
;
box-sizing
:
border-box
;
padding
:
20mm
;
}
.no-print
{
display
:
none
!important
;
}
}
</
style
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment