开发: 聊天页面修改

This commit is contained in:
zhengw
2023-04-12 10:10:16 +08:00
parent ce3d942371
commit d67758d361
13 changed files with 416 additions and 52 deletions

View File

@@ -6,12 +6,15 @@ import { ChatFile } from './ChatFile';
import { ChatImage } from './ChatImage'; import { ChatImage } from './ChatImage';
import { ChatLink } from './ChatLink'; import { ChatLink } from './ChatLink';
import { ChatLocation } from './ChatLocation'; import { ChatLocation } from './ChatLocation';
import { ChatMeetingVoiceCall } from './ChatMeetingVoiceCall';
import { ChatRecord } from './ChatRecord'; import { ChatRecord } from './ChatRecord';
import { ChatRedpacket } from './ChatRedpacket'; import { ChatRedpacket } from './ChatRedpacket';
import { ChatRevoke } from './ChatRevoke'; import { ChatRevoke } from './ChatRevoke';
import { ChatText } from './ChatText'; import { ChatText } from './ChatText';
import { ChatVideo } from './ChatVideo'; import { ChatVideo } from './ChatVideo';
import { ChatVoice } from './ChatVoice'; import { ChatVoice } from './ChatVoice';
import { ChatVoiptext } from './ChatVoiptext';
import { ChatWeapp } from './ChatWeapp';
export const ChatBar: React.FC<IChatItem> = (props) => { export const ChatBar: React.FC<IChatItem> = (props) => {
const { from, to, chat } = props; const { from, to, chat } = props;
@@ -37,6 +40,12 @@ export const ChatBar: React.FC<IChatItem> = (props) => {
return <ChatVoice from={from} to={to} chat={chat}></ChatVoice>; return <ChatVoice from={from} to={to} chat={chat}></ChatVoice>;
} else if (chat?.msg_type == 'chatrecord') { } else if (chat?.msg_type == 'chatrecord') {
return <ChatRecord from={from} to={to} chat={chat}></ChatRecord>; return <ChatRecord from={from} to={to} chat={chat}></ChatRecord>;
} else if (chat?.msg_type == 'meeting_voice_call') {
return <ChatMeetingVoiceCall from={from} to={to} chat={chat}></ChatMeetingVoiceCall>;
} else if (chat?.msg_type == 'voiptext') {
return <ChatVoiptext from={from} to={to} chat={chat}></ChatVoiptext>;
} else if (chat?.msg_type == 'weapp') {
return <ChatWeapp from={from} to={to} chat={chat}></ChatWeapp>;
} else if (chat?.msg_type == 'revoke') { } else if (chat?.msg_type == 'revoke') {
return <ChatRevoke></ChatRevoke>; return <ChatRevoke></ChatRevoke>;
} else if (chat?.msg_type == 'agree' || chat?.msg_type == 'disagree') { } else if (chat?.msg_type == 'agree' || chat?.msg_type == 'disagree') {

View File

@@ -1,15 +1,18 @@
import React from 'react'; import { Image } from 'antd';
import React, { useState } from 'react';
import { IChatItem } from '../ChatLogsType'; import { IChatItem } from '../ChatLogsType';
import styles from './index.module.scss'; import styles from './index.module.scss';
// 表情 // 表情
export const ChatEmotion: React.FC<IChatItem> = (props) => { export const ChatEmotion: React.FC<IChatItem> = (props) => {
const [visible, setVisible] = useState(false);
function content() { function content() {
try { try {
const msg = JSON.parse(props.chat?.content as string); const msg = JSON.parse(props.chat?.content as string);
return ( return (
<div <div
className={styles.imgPreview}
style={{ style={{
display: 'flex', display: 'flex',
color: '#000', color: '#000',
@@ -18,7 +21,13 @@ export const ChatEmotion: React.FC<IChatItem> = (props) => {
justifyContent: 'center', justifyContent: 'center',
}} }}
> >
<img <Image
preview={{
visible: visible,
onVisibleChange: (value) => {
setVisible(value);
},
}}
src={`/api/${msg.path}`} src={`/api/${msg.path}`}
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }} style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }}
alt="" alt=""

View File

@@ -1,13 +1,17 @@
import { Image } from 'antd';
import { useState } from 'react';
import { IChatItem } from '../ChatLogsType'; import { IChatItem } from '../ChatLogsType';
import styles from './index.module.scss'; import styles from './index.module.scss';
export const ChatImage: React.FC<IChatItem> = (props) => { export const ChatImage: React.FC<IChatItem> = (props) => {
const [visible, setVisible] = useState(false);
function content() { function content() {
try { try {
const msg = JSON.parse(props.chat?.content as string); const msg = JSON.parse(props.chat?.content as string);
return ( return (
<div <div
className={styles.imgPreview}
style={{ style={{
display: 'flex', display: 'flex',
color: '#000', color: '#000',
@@ -16,7 +20,13 @@ export const ChatImage: React.FC<IChatItem> = (props) => {
justifyContent: 'center', justifyContent: 'center',
}} }}
> >
<img <Image
preview={{
visible: visible,
onVisibleChange: (value, prevValue, currentIndex) => {
setVisible(value);
},
}}
src={`/api/${msg.path}`} src={`/api/${msg.path}`}
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }} style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }}
alt="" alt=""

View File

@@ -0,0 +1,50 @@
import { IChatItem } from '../ChatLogsType';
import styles from './index.module.scss';
export const ChatMeetingVoiceCall: React.FC<IChatItem> = (props) => {
function content() {
try {
const msg = JSON.parse(props.chat?.content as string);
// todo console.log(msg);
return (
<a
style={{ display: 'flex', color: '#000' }}
download={msg.file_name}
href={`/api/${msg.path}`}
>
<div style={{ wordBreak: 'break-all', paddingRight: 8 }}>// TODO 音频存档</div>
</a>
);
} catch (_e) {
return (
<div>
<div style={{ wordBreak: 'break-all', paddingRight: 8 }}>{props.chat?.content}</div>
</div>
);
}
}
return (
<>
{props.from?.user_id == props.chat?.msg_from ? (
<div className={styles.chatRight}>
<div className={styles.chatContentBox}>
<div className={styles.name}>{props.from?.name}</div>
<div className={styles.content}>{content()}</div>
</div>
<div className={styles.chatAvatar}>{props.from?.name[0]}</div>
</div>
) : (
<div className={styles.chatLeft}>
<div className={styles.chatAvatar}>
<img src={props.to?.cust_info.avatar} alt="" />
</div>
<div className={styles.chatContentBox}>
<div className={styles.name}>{props.to?.cust_info.name}</div>
<div className={styles.content}>{content()}</div>
</div>
</div>
)}
</>
);
};

View File

@@ -1,26 +1,79 @@
import { FileTextFilled } from '@ant-design/icons'; import { Modal } from 'antd';
import { useEffect, useState } from 'react';
import { IChatItem } from '../ChatLogsType'; import { IChatItem } from '../ChatLogsType';
import styles from './index.module.scss'; import styles from './index.module.scss';
export const ChatRecord: React.FC<IChatItem> = (props) => { export const ChatRecord: React.FC<IChatItem> = (props) => {
const [visible, setVisible] = useState(false);
function chatRecordContent(data: any) {
// todo console.log(msg);
if (data.type == 'ChatRecordText') {
const content = JSON.parse(data.content);
return <div>{content.content}</div>;
} else if (data.type == 'ChatRecordImage') {
return <div>[]</div>;
} else if (data.type == 'ChatRecordFile') {
return <div>[]</div>;
} else if (data.type == 'ChatRecordVideo') {
return <div>[]</div>;
} else if (data.type == 'ChatRecordLink') {
return <div>[]</div>;
} else if (data.type == 'ChatRecordLocation') {
return <div>[]</div>;
} else if (data.type == 'ChatRecordMixed') {
return <div>[]</div>;
}
return <div>[]</div>;
}
const [record, setRecord] = useState<any>(null);
useEffect(() => {
try {
const msg = JSON.parse(props.chat?.content as string);
setRecord(msg);
} catch (e) {}
}, [props.chat]);
function content() { function content() {
try { try {
const msg = JSON.parse(props.chat?.content as string); const msg = JSON.parse(props.chat?.content as string);
// setRecord(msg);
console.log(msg);
return ( return (
<a <div
style={{ display: 'flex', color: '#000' }} style={{
download={msg.file_name} display: 'flex',
href={`/api/${msg.path}`} color: '#000',
flexDirection: 'column',
minWidth: 260,
cursor: 'pointer',
}}
onClick={() => {
setVisible(true);
}}
> >
<div style={{ wordBreak: 'break-all', paddingRight: 8 }}>{msg.file_name}</div> <div style={{ wordBreak: 'break-all', fontSize: 16 }}>{msg.title}</div>
<FileTextFilled style={{ fontSize: 40, color: '#FA9D3B', flexShrink: 0 }} /> {msg.item[0] ? (
</a> <div style={{ color: '#999' }}>{chatRecordContent(msg.item[0])}</div>
) : null}
{msg.item[1] ? (
<div style={{ color: '#999' }}>{chatRecordContent(msg.item[1])}</div>
) : null}
{msg.item[2] ? (
<div style={{ color: '#999' }}>{chatRecordContent(msg.item[2])}</div>
) : null}
<div style={{ borderTop: '1px solid #ddd', color: '#999', marginTop: 8, paddingTop: 8 }}>
</div>
</div>
); );
} catch (_e) { } catch (_e) {
return ( return (
<div> <div>
<div style={{ wordBreak: 'break-all', paddingRight: 8 }}>{props.chat?.content}</div> <div style={{ wordBreak: 'break-all', paddingRight: 8 }}>{props.chat?.content}</div>
<FileTextFilled style={{ fontSize: 40, color: '#FA9D3B', flexShrink: 0 }} />
</div> </div>
); );
} }
@@ -28,6 +81,32 @@ export const ChatRecord: React.FC<IChatItem> = (props) => {
return ( return (
<> <>
<Modal
open={visible}
footer={false}
title={record ? record.title : '无标题'}
onCancel={() => {
setVisible(false);
}}
>
<div>
{record ? (
<div>
{record.item.map((item: any) => {
console.log(item);
return (
<div
style={{ padding: '8px 0', borderBottom: '1px solid #eee' }}
key={`${item.msgtime}_${item.type}`}
>
{chatRecordContent(item)}
</div>
);
})}
</div>
) : null}
</div>
</Modal>
{props.from?.user_id == props.chat?.msg_from ? ( {props.from?.user_id == props.chat?.msg_from ? (
<div className={styles.chatRight}> <div className={styles.chatRight}>
<div className={styles.chatContentBox}> <div className={styles.chatContentBox}>

View File

@@ -16,9 +16,9 @@ export const ChatTime: React.FC<IChatTimeProps> = (props) => {
const yesterday = new Date(); const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1); yesterday.setDate(yesterday.getDate() - 1);
const dayDiff = const dayDiff =
(Date.parse( (new Date(
`${now.getFullYear()}-${padWith(now.getMonth() + 1)}-${padWith(now.getDate())} 00:00:00`, `${now.getFullYear()}-${padWith(now.getMonth() + 1)}-${padWith(now.getDate())} 00:00:00`,
) - ).getTime() -
new Date(props.msgtime).getTime()) / new Date(props.msgtime).getTime()) /
(24 * 60 * 60 * 1000) + (24 * 60 * 60 * 1000) +
1; 1;
@@ -58,7 +58,7 @@ export const ChatTime: React.FC<IChatTimeProps> = (props) => {
}; };
return ( return (
<div style={{ display: 'flex', justifyContent: 'center' }}> <div style={{ display: 'flex', justifyContent: 'center', marginBottom: 12 }}>
<div <div
style={{ style={{
background: '#DADADA', background: '#DADADA',

View File

@@ -1,8 +1,13 @@
import { PlayCircleOutlined } from '@ant-design/icons'; import { CloseOutlined, PlayCircleOutlined } from '@ant-design/icons';
import { useRef, useState } from 'react';
import { IChatItem } from '../ChatLogsType'; import { IChatItem } from '../ChatLogsType';
import styles from './index.module.scss'; import styles from './index.module.scss';
export const ChatVideo: React.FC<IChatItem> = (props) => { export const ChatVideo: React.FC<IChatItem> = (props) => {
const [open, setOpen] = useState(false);
const videoRef = useRef<any>();
function playLen(v: number) { function playLen(v: number) {
const len = Number(v) || 0; const len = Number(v) || 0;
if (len > 60) { if (len > 60) {
@@ -19,10 +24,58 @@ export const ChatVideo: React.FC<IChatItem> = (props) => {
const msg = JSON.parse(props.chat?.content as string); const msg = JSON.parse(props.chat?.content as string);
// console.log(msg); // console.log(msg);
return ( return (
<a className={styles.video} href={`/api/${msg.path}`} target="_blank"> <>
<div
className={styles.video}
onClick={() => {
setOpen(true);
videoRef.current.play().catch((error) => {
console.log(error);
});
}}
>
<PlayCircleOutlined style={{ color: '#fff', fontSize: 40 }} /> <PlayCircleOutlined style={{ color: '#fff', fontSize: 40 }} />
<span className={styles.videoTime}>{playLen(msg.play_length)}</span> <span className={styles.videoTime}>{playLen(msg.play_length)}</span>
</a> </div>
<div
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
background: 'rgba(0 ,0, 0, 0.45)',
zIndex: 1000,
display: open ? 'flex' : 'none',
justifyContent: 'center',
alignItems: 'center',
}}
onClick={(e) => {
setOpen(false);
videoRef.current.pause();
}}
>
<CloseOutlined
style={{
position: 'absolute',
top: 12,
right: 12,
color: '#fff',
fontSize: 26,
cursor: 'pointer',
}}
/>
<video
ref={videoRef}
style={{ maxWidth: '100%', maxHeight: '100%' }}
src={`/api/${msg.path}`}
controls
onClick={(e) => {
e.stopPropagation();
}}
></video>
</div>
</>
); );
} catch (_e) { } catch (_e) {
return ( return (

View File

@@ -1,14 +1,39 @@
import { FileTextFilled } from '@ant-design/icons'; import { FileTextFilled } from '@ant-design/icons';
import { useRef, useState } from 'react';
import { IChatItem } from '../ChatLogsType'; import { IChatItem } from '../ChatLogsType';
import styles from './index.module.scss'; import styles from './index.module.scss';
export const ChatVoice: React.FC<IChatItem> = (props) => { export const ChatVoice: React.FC<IChatItem> = (props) => {
const [open, setOpen] = useState(false);
const audioRef = useRef<any>();
function content() { function content() {
try { try {
const msg = JSON.parse(props.chat?.content as string); const msg = JSON.parse(props.chat?.content as string);
return ( return (
<a style={{ display: 'flex', color: '#000' }} href={`/api/${msg.path}`} target="_blank"> <div style={{ display: 'flex', flexDirection: 'column' }}>
<div
style={{
display: 'flex',
color: '#000',
justifyContent: 'flex-end',
alignItems: 'center',
marginBottom: 4,
}}
onClick={() => {
const audios = document.querySelectorAll('audio');
if (audios) {
audios.forEach((el) => {
el.pause();
});
}
audioRef.current.play().catch((error: any) => {
console.log(error);
});
setOpen(true);
}}
>
<span style={{ lineHeight: 1, marginRight: 4, minWidth: 100, textAlign: 'right' }}> <span style={{ lineHeight: 1, marginRight: 4, minWidth: 100, textAlign: 'right' }}>
{msg.play_length}" {msg.play_length}"
</span> </span>
@@ -18,7 +43,14 @@ export const ChatVoice: React.FC<IChatItem> = (props) => {
fill="#000000" fill="#000000"
></path> ></path>
</svg> </svg>
</a> </div>
<audio
ref={audioRef}
src={`/api/${msg.path}`}
style={{ display: `${open ? 'block' : 'none'}` }}
controls
></audio>
</div>
); );
} catch (_e) { } catch (_e) {
return ( return (

View File

@@ -0,0 +1,50 @@
import { durationFormat } from '@/services/utils';
import { IChatItem } from '../ChatLogsType';
import styles from './index.module.scss';
export const ChatVoiptext: React.FC<IChatItem> = (props) => {
function content() {
try {
// todo ///
const msg = JSON.parse(props.chat?.content as string);
// console.log(msg);
return (
<div style={{ display: 'flex', color: '#000' }}>
<div style={{ wordBreak: 'break-all', paddingRight: 8 }}>
{durationFormat(msg.callduration)}
</div>
</div>
);
} catch (_e) {
return (
<div>
<div style={{ wordBreak: 'break-all', paddingRight: 8 }}>{props.chat?.content}</div>
</div>
);
}
}
return (
<>
{props.from?.user_id == props.chat?.msg_from ? (
<div className={styles.chatRight}>
<div className={styles.chatContentBox}>
<div className={styles.name}>{props.from?.name}</div>
<div className={styles.content}>{content()}</div>
</div>
<div className={styles.chatAvatar}>{props.from?.name[0]}</div>
</div>
) : (
<div className={styles.chatLeft}>
<div className={styles.chatAvatar}>
<img src={props.to?.cust_info.avatar} alt="" />
</div>
<div className={styles.chatContentBox}>
<div className={styles.name}>{props.to?.cust_info.name}</div>
<div className={styles.content}>{content()}</div>
</div>
</div>
)}
</>
);
};

View File

@@ -0,0 +1,54 @@
import { FileTextFilled } from '@ant-design/icons';
import { IChatItem } from '../ChatLogsType';
import styles from './index.module.scss';
export const ChatWeapp: React.FC<IChatItem> = (props) => {
function content() {
try {
const msg = JSON.parse(props.chat?.content as string);
console.log(msg);
return (
<a
style={{ display: 'flex', color: '#000' }}
download={msg.file_name}
href={`/api/${msg.path}`}
>
<div style={{ wordBreak: 'break-all', paddingRight: 8 }}>{msg.file_name}</div>
<FileTextFilled style={{ fontSize: 40, color: '#FA9D3B', flexShrink: 0 }} />
</a>
);
} catch (_e) {
return (
<div>
<div style={{ wordBreak: 'break-all', paddingRight: 8 }}>{props.chat?.content}</div>
<FileTextFilled style={{ fontSize: 40, color: '#FA9D3B', flexShrink: 0 }} />
</div>
);
}
}
return (
<>
{props.from?.user_id == props.chat?.msg_from ? (
<div className={styles.chatRight}>
<div className={styles.chatContentBox}>
<div className={styles.name}>{props.from?.name}</div>
<div className={styles.content}>{content()}</div>
</div>
<div className={styles.chatAvatar}>{props.from?.name[0]}</div>
</div>
) : (
<div className={styles.chatLeft}>
<div className={styles.chatAvatar}>
<img src={props.to?.cust_info.avatar} alt="" />
</div>
<div className={styles.chatContentBox}>
<div className={styles.name}>{props.to?.cust_info.name}</div>
<div className={styles.content}>{content()}</div>
</div>
</div>
)}
</>
);
};

View File

@@ -12,7 +12,6 @@
} }
.chatAvatar { .chatAvatar {
flex-shrink: 0; flex-shrink: 0;
width: 34px; width: 34px;
height: 34px; height: 34px;
@@ -109,3 +108,11 @@
color: #fff; color: #fff;
} }
} }
.imgPreview {
& > div {
display: flex;
justify-content: center;
align-items: center;
}
}

View File

@@ -124,9 +124,9 @@ const ChatLogs: React.FC = () => {
if (res.err_code == 0) { if (res.err_code == 0) {
if (Array.isArray(res.data)) { if (Array.isArray(res.data)) {
if (param.curr_page == 1) { if (param.curr_page == 1) {
setChatLogs([...res.data, { curr_page: param.curr_page }]); setChatLogs([...res.data.reverse(), { curr_page: param.curr_page }]);
} else { } else {
setChatLogs([...res.data, { curr_page: param.curr_page }, ...chatLogs]); setChatLogs([...res.data.reverse(), { curr_page: param.curr_page }, ...chatLogs]);
} }
} }
} }
@@ -270,28 +270,28 @@ const ChatLogs: React.FC = () => {
></div> ></div>
); );
} else { } else {
if (timeDiffRef.current == '') { // if (timeDiffRef.current == '') {
timeDiffRef.current = item.msg_time; // timeDiffRef.current = item.msg_time;
timeShowRef.current = false; // timeShowRef.current = false;
} else { // } else {
if ( // if (
Date.parse(item.msg_time) - Date.parse(timeDiffRef.current) > // new Date(item.msg_time).getTime() - new Date(timeDiffRef.current).getTime() >
5 * 60 * 1000 // 5 * 60 * 1000
) { // ) {
timeDiffRef.current = item.msg_time; // timeDiffRef.current = item.msg_time;
timeShowRef.current = true; // timeShowRef.current = true;
} else { // } else {
timeShowRef.current = false; // timeShowRef.current = false;
} // }
} // }
if (i == 0) { // if (i == 0) {
timeShowRef.current = true; // timeShowRef.current = true;
} // }
return ( return (
<div key={item.msg_id}> <div key={item.msg_id}>
{timeShowRef.current ? <ChatTime msgtime={item.msg_time}></ChatTime> : null} <ChatTime msgtime={item.msg_time}></ChatTime>
<ChatBar from={selectStaff} to={selectCustFollow} chat={item}></ChatBar> <ChatBar from={selectStaff} to={selectCustFollow} chat={item}></ChatBar>
</div> </div>
); );

View File

@@ -35,3 +35,14 @@ export const notificationError = (message: IError) => {
console.log('window.NotificationCF未绑定antd的notification'); console.log('window.NotificationCF未绑定antd的notification');
} }
}; };
export const durationFormat = (v: number) => {
const len = Number(v) || 0;
if (len > 60) {
return `${Math.floor(len / 60)
.toString()
.padStart(2, '0')}:${(len % 60).toString().padStart(2, '0')}`;
} else {
return `00:${len.toString().padStart(2, '0')}`;
}
};