触客宝右边栏提交

This commit is contained in:
wong
2025-08-30 17:07:53 +08:00
parent f6837f7819
commit b9d88160a2
5 changed files with 115 additions and 81 deletions

View File

@@ -18,7 +18,7 @@
.closeButton { .closeButton {
color: #8c8c8c; color: #8c8c8c;
&:hover { &:hover {
color: #262626; color: #262626;
background: #f5f5f5; background: #f5f5f5;
@@ -44,7 +44,7 @@
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
color: #262626; color: #262626;
max-width: 200px; max-width: 100%;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
@@ -52,12 +52,12 @@
.profileRemark { .profileRemark {
margin-bottom: 12px; margin-bottom: 12px;
.remarkText { .remarkText {
color: #8c8c8c; color: #8c8c8c;
font-size: 14px; font-size: 14px;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
color: #1890ff; color: #1890ff;
} }
@@ -107,7 +107,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 12px; margin-bottom: 12px;
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
@@ -131,6 +131,20 @@
font-size: 14px; font-size: 14px;
flex: 1; flex: 1;
word-break: break-all; word-break: break-all;
// 备注编辑区域样式
:global(.ant-input) {
font-size: 14px;
}
:global(.ant-btn) {
font-size: 12px;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
} }
} }
@@ -169,7 +183,7 @@
@media (max-width: 768px) { @media (max-width: 768px) {
.profileSider { .profileSider {
width: 280px !important; width: 280px !important;
.profileContainer { .profileContainer {
padding: 12px; padding: 12px;
} }
@@ -189,7 +203,7 @@
.infoItem { .infoItem {
margin-bottom: 10px; margin-bottom: 10px;
.infoLabel { .infoLabel {
width: 50px; width: 50px;
font-size: 13px; font-size: 13px;
@@ -201,4 +215,4 @@
} }
} }
} }
} }

View File

@@ -43,14 +43,33 @@ const Person: React.FC<PersonProps> = ({
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
const [isEditingRemark, setIsEditingRemark] = useState(false); const [isEditingRemark, setIsEditingRemark] = useState(false);
const [remarkValue, setRemarkValue] = useState(contract.conRemark || ""); const [remarkValue, setRemarkValue] = useState(contract.conRemark || "");
const [selectedTags, setSelectedTags] = useState<string[]>(contract.labels || []);
const [allAvailableTags, setAllAvailableTags] = useState<string[]>([]);
const kfSelectedUser = useCkChatStore(state => state.kfSelectedUser()); const kfSelectedUser = useCkChatStore(state => state.kfSelectedUser(contract.wechatAccountId || 0));
// 当contract变化时更新备注值 // 获取所有可用标签
useEffect(() => {
const fetchAvailableTags = async () => {
try {
// 从kfSelectedUser.labels和contract.labels合并获取所有标签
const kfTags = kfSelectedUser?.labels || [];
const contractTags = contract.labels || [];
const allTags = [...new Set([...kfTags, ...contractTags])];
setAllAvailableTags(allTags);
} catch (error) {
console.error('获取标签失败:', error);
}
};
fetchAvailableTags();
}, [kfSelectedUser, contract.labels]);
// 当contract变化时更新备注值和标签
useEffect(() => { useEffect(() => {
setRemarkValue(contract.conRemark || ""); setRemarkValue(contract.conRemark || "");
setIsEditingRemark(false); setIsEditingRemark(false);
}, [contract.conRemark]); setSelectedTags(contract.labels || []);
}, [contract.conRemark, contract.labels]);
// 处理备注保存 // 处理备注保存
const handleSaveRemark = () => { const handleSaveRemark = () => {
@@ -67,6 +86,18 @@ const Person: React.FC<PersonProps> = ({
setIsEditingRemark(false); setIsEditingRemark(false);
}; };
// 处理标签点击切换
const handleTagToggle = (tagName: string) => {
const newSelectedTags = selectedTags.includes(tagName)
? selectedTags.filter(tag => tag !== tagName)
: [...selectedTags, tagName];
setSelectedTags(newSelectedTags);
// 这里应该调用API保存标签到后端
messageApi.success(`标签"${tagName}"${selectedTags.includes(tagName) ? '已取消' : '已选中'}`);
};
// 模拟联系人详细信息 // 模拟联系人详细信息
const contractInfo = { const contractInfo = {
name: contract.name, name: contract.name,
@@ -80,10 +111,10 @@ const Person: React.FC<PersonProps> = ({
department: contract.department || "-", department: contract.department || "-",
position: contract.position || "-", position: contract.position || "-",
company: contract.company || "-", company: contract.company || "-",
location: contract.location || "-", region: contract.region || "-",
joinDate: contract.joinDate || "-", joinDate: contract.joinDate || "-",
status: "在线", status: "在线",
tags: contract.labels, tags: selectedTags,
bio: contract.bio || "-", bio: contract.bio || "-",
}; };
@@ -123,8 +154,36 @@ const Person: React.FC<PersonProps> = ({
</h4> </h4>
</Tooltip> </Tooltip>
<div className={styles.profileRemark}>
{JSON.stringify(kfSelectedUser)}
<div className={styles.profileStatus}>
<span className={styles.statusDot}></span>
{contractInfo.status}
</div>
</div>
</div>
{/* 详细信息卡片 */}
<Card title="详细信息" className={styles.profileCard}>
<div className={styles.infoItem}>
<TeamOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.alias || contractInfo.wechatId}</span>
</div>
<div className={styles.infoItem}>
<PhoneOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.phone}</span>
</div>
<div className={styles.infoItem}>
<EnvironmentOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.region}</span>
</div>
<div className={styles.infoItem}>
<EditOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<div className={styles.infoValue}>
{isEditingRemark ? ( {isEditingRemark ? (
<div <div
style={{ style={{
@@ -163,7 +222,7 @@ const Person: React.FC<PersonProps> = ({
gap: "8px", gap: "8px",
}} }}
> >
<span className={styles.remarkText}> <span>
{contractInfo.conRemark || "点击添加备注"} {contractInfo.conRemark || "点击添加备注"}
</span> </span>
<Button <Button
@@ -175,73 +234,33 @@ const Person: React.FC<PersonProps> = ({
</div> </div>
)} )}
</div> </div>
<div className={styles.profileStatus}>
<span className={styles.statusDot}></span>
{contractInfo.status}
</div>
</div>
</div>
{/* 详细信息卡片 */}
<Card title="详细信息" className={styles.profileCard}>
<div className={styles.infoItem}>
<TeamOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.wechatId}</span>
</div>
<div className={styles.infoItem}>
<UserOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.alias}</span>
</div>
<div className={styles.infoItem}>
<PhoneOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.phone}</span>
</div>
<div className={styles.infoItem}>
<MailOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.email}</span>
</div>
<div className={styles.infoItem}>
<BankOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>
{contractInfo.department}
</span>
</div>
<div className={styles.infoItem}>
<StarOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.position}</span>
</div>
<div className={styles.infoItem}>
<BankOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.company}</span>
</div>
<div className={styles.infoItem}>
<EnvironmentOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.location}</span>
</div>
<div className={styles.infoItem}>
<CalendarOutlined className={styles.infoIcon} />
<span className={styles.infoLabel}>:</span>
<span className={styles.infoValue}>{contractInfo.joinDate}</span>
</div> </div>
</Card> </Card>
{/* 标签 */} {/* 标签 */}
<Card title="标签" className={styles.profileCard}> <Card title="标签" className={styles.profileCard}>
<div className={styles.tagsContainer}> <div className={styles.tagsContainer}>
{contractInfo.tags?.map((tag, index) => ( {/* 渲染所有可用标签,选中的排在前面 */}
<Tag key={index} color="blue"> {[...new Set([...selectedTags, ...allAvailableTags])].map((tag, index) => {
{tag} const isSelected = selectedTags.includes(tag);
</Tag> return (
))} <Tag
key={index}
color={isSelected ? "blue" : "default"}
style={{
cursor: 'pointer',
border: isSelected ? '1px solid #1890ff' : '1px solid #d9d9d9',
backgroundColor: isSelected ? '#e6f7ff' : '#fafafa'
}}
onClick={() => handleTagToggle(tag)}
>
{tag}
</Tag>
);
})}
{allAvailableTags.length === 0 && (
<span style={{ color: '#999', fontSize: '12px' }}></span>
)}
</div> </div>
</Card> </Card>

View File

@@ -68,7 +68,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
>({}); >({});
const messagesEndRef = useRef<HTMLDivElement>(null); const messagesEndRef = useRef<HTMLDivElement>(null);
const kfSelectedUser = useCkChatStore(state => state.kfSelectedUser()); const kfSelectedUser = useCkChatStore(state => state.kfSelectedUser(contract.wechatAccountId || 0));
useEffect(() => { useEffect(() => {
clearUnreadCount([contract.id]).then(() => { clearUnreadCount([contract.id]).then(() => {
setLoading(true); setLoading(true);

View File

@@ -31,7 +31,7 @@ export interface CkChatState {
chatSessions: any[]; chatSessions: any[];
kfUserList: KfUserListData[]; kfUserList: KfUserListData[];
kfSelected: number; kfSelected: number;
kfSelectedUser: () => KfUserListData | undefined; kfSelectedUser: (kfId: number) => KfUserListData | undefined;
newContractList: { groupName: string; contacts: any[] }[]; newContractList: { groupName: string; contacts: any[] }[];
asyncKfSelected: (data: number) => void; asyncKfSelected: (data: number) => void;
getkfUserList: () => KfUserListData[]; getkfUserList: () => KfUserListData[];

View File

@@ -16,9 +16,10 @@ export const useCkChatStore = createPersistStore<CkChatState>(
kfUserList: [], //客服列表 kfUserList: [], //客服列表
kfSelected: 0, kfSelected: 0,
newContractList: [], //联系人分组 newContractList: [], //联系人分组
kfSelectedUser: () => { kfSelectedUser: (kfId:number) => {
const state = useCkChatStore.getState(); const state = useCkChatStore.getState();
return state.kfUserList.find(item => item.id === state.kfSelected);
return state.kfUserList.find(item => item.id === (kfId));
}, },
asyncKfSelected: (data: number) => { asyncKfSelected: (data: number) => {
set({ kfSelected: data }); set({ kfSelected: data });