refactor(archive): 优化列表展示体验,补全客户昵称与添加悬浮提示

1. 前端:为发送人、接收人列添加悬浮提示展示完整ID,移除无用的消息动作列
2. 后端:新增企微客户昵称补全逻辑,实时拉取并更新数据库中缺失的客户名称
This commit is contained in:
jqb 2026-07-20 13:38:32 +08:00
parent 75c98d04e6
commit 26fadbe307
2 changed files with 56 additions and 20 deletions

View File

@ -1,5 +1,6 @@
package com.artedu.archive.service; package com.artedu.archive.service;
import com.artedu.archive.client.WeComApiClient;
import com.artedu.archive.entity.ArchiveMessage; import com.artedu.archive.entity.ArchiveMessage;
import com.artedu.archive.entity.Customer; import com.artedu.archive.entity.Customer;
import com.artedu.archive.entity.Staff; import com.artedu.archive.entity.Staff;
@ -44,6 +45,9 @@ public class ArchiveSessionService {
@Autowired @Autowired
private ArchiveMessageMapper archiveMessageMapper; private ArchiveMessageMapper archiveMessageMapper;
@Autowired
private WeComApiClient weComApiClient;
private static final java.time.format.DateTimeFormatter TIME_FORMATTER = private static final java.time.format.DateTimeFormatter TIME_FORMATTER =
java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@ -80,9 +84,48 @@ public class ArchiveSessionService {
Page<Customer> pageResult = customerMapper.selectPage(new Page<>(page, size), wrapper); Page<Customer> pageResult = customerMapper.selectPage(new Page<>(page, size), wrapper);
List<CustomerSessionVO> list = pageResult.getRecords().stream().map(this::convertCustomerVO).collect(Collectors.toList()); List<CustomerSessionVO> list = pageResult.getRecords().stream().map(this::convertCustomerVO).collect(Collectors.toList());
enrichCustomerNames(list);
return PageResult.of(page, size, pageResult.getTotal(), list); return PageResult.of(page, size, pageResult.getTotal(), list);
} }
/**
* 补全客户昵称:对名字为空或仍为 ID 的外部客户,实时从企微获取并更新数据库
*/
private void enrichCustomerNames(List<CustomerSessionVO> list) {
if (list == null || list.isEmpty()) {
return;
}
for (CustomerSessionVO vo : list) {
String customerId = vo.getCustomerId();
String name = vo.getName();
if (!isExternalUserId(customerId) || (name != null && !name.trim().isEmpty() && !customerId.equals(name))) {
continue;
}
try {
Map<String, Object> detail = weComApiClient.getExternalContact(customerId);
if (detail == null || detail.isEmpty()) {
continue;
}
Map<String, Object> contact = (Map<String, Object>) detail.get("external_contact");
if (contact == null) {
continue;
}
String fetchedName = (String) contact.get("name");
if (fetchedName == null || fetchedName.trim().isEmpty()) {
continue;
}
vo.setName(fetchedName);
Customer customer = customerMapper.selectByCustomerId(customerId, corpId);
if (customer != null) {
customer.setName(fetchedName);
customerMapper.updateById(customer);
}
} catch (Exception e) {
log.warn("实时获取客户昵称失败: customerId={}", customerId, e);
}
}
}
/** /**
* 员工的会话列表 * 员工的会话列表
*/ */

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { Table, Input, Select, Tag, Space, Card, Statistic, Row, Col, Button } from 'antd' import { Table, Input, Select, Tag, Space, Card, Statistic, Row, Col, Button, Tooltip } from 'antd'
import { MessageOutlined, FileTextOutlined, PictureOutlined, AudioOutlined, VideoCameraOutlined, FileOutlined, LinkOutlined, EnvironmentOutlined, SmileOutlined, AppstoreOutlined, WechatOutlined } from '@ant-design/icons' import { MessageOutlined, FileTextOutlined, PictureOutlined, AudioOutlined, VideoCameraOutlined, FileOutlined, LinkOutlined, EnvironmentOutlined, SmileOutlined, AppstoreOutlined, WechatOutlined } from '@ant-design/icons'
import request from '../utils/request' import request from '../utils/request'
@ -45,12 +45,6 @@ export default function ArchiveList() {
EXTERNAL: '客户', EXTERNAL: '客户',
} }
const actionMap: Record<string, string> = {
send: '发送消息',
recall: '撤回消息',
switch: '切换企业',
}
const loadData = async (page = 1, pageSize = 20) => { const loadData = async (page = 1, pageSize = 20) => {
setLoading(true) setLoading(true)
try { try {
@ -260,7 +254,9 @@ export default function ArchiveList() {
dataIndex: 'fromUser', dataIndex: 'fromUser',
width: 120, width: 120,
render: (fromUser: string, record: any) => ( render: (fromUser: string, record: any) => (
<span style={{ color: '#333', fontWeight: 500 }}>{record.fromUserName || fromUser}</span> <Tooltip title={fromUser} placement="topLeft">
<span style={{ color: '#333', fontWeight: 500 }}>{record.fromUserName || fromUser}</span>
</Tooltip>
), ),
}, },
{ {
@ -280,23 +276,20 @@ export default function ArchiveList() {
width: 160, width: 160,
render: (time: string) => <span style={{ color: '#666', fontSize: 13 }}>{time}</span>, render: (time: string) => <span style={{ color: '#666', fontSize: 13 }}>{time}</span>,
}, },
{
title: '消息动作',
dataIndex: 'action',
width: 90,
align: 'center' as const,
render: (action: string) => <span style={{ color: '#666' }}>{actionMap[action] || action}</span>,
},
{ {
title: '接收人', title: '接收人',
dataIndex: 'toUser', dataIndex: 'toUser',
width: 200, width: 200,
ellipsis: true, ellipsis: true,
render: (_: any, record: any) => ( render: (_: any, record: any) => {
<span style={{ color: '#666', fontSize: 13 }} title={parseReceivers(record.toUser, record.tolist)}> const ids = parseReceivers(record.toUser, record.tolist)
{parseReceivers(record.toUser, record.tolist)} const names = record.toUserName || ids
</span> return (
), <Tooltip title={ids} placement="topLeft">
<span style={{ color: '#666', fontSize: 13 }}>{names}</span>
</Tooltip>
)
},
}, },
] ]