开发: 聊天页面修改

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 { ChatLink } from './ChatLink';
import { ChatLocation } from './ChatLocation';
import { ChatMeetingVoiceCall } from './ChatMeetingVoiceCall';
import { ChatRecord } from './ChatRecord';
import { ChatRedpacket } from './ChatRedpacket';
import { ChatRevoke } from './ChatRevoke';
import { ChatText } from './ChatText';
import { ChatVideo } from './ChatVideo';
import { ChatVoice } from './ChatVoice';
import { ChatVoiptext } from './ChatVoiptext';
import { ChatWeapp } from './ChatWeapp';
export const ChatBar: React.FC<IChatItem> = (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>;
} else if (chat?.msg_type == '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') {
return <ChatRevoke></ChatRevoke>;
} 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 styles from './index.module.scss';
// 表情
export const ChatEmotion: React.FC<IChatItem> = (props) => {
const [visible, setVisible] = useState(false);
function content() {
try {
const msg = JSON.parse(props.chat?.content as string);
return (
<div
className={styles.imgPreview}
style={{
display: 'flex',
color: '#000',
@@ -18,7 +21,13 @@ export const ChatEmotion: React.FC<IChatItem> = (props) => {
justifyContent: 'center',
}}
>
<img
<Image
preview={{
visible: visible,
onVisibleChange: (value) => {
setVisible(value);
},
}}
src={`/api/${msg.path}`}
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }}
alt=""

View File

@@ -1,13 +1,17 @@
import { Image } from 'antd';
import { useState } from 'react';
import { IChatItem } from '../ChatLogsType';
import styles from './index.module.scss';
export const ChatImage: React.FC<IChatItem> = (props) => {
const [visible, setVisible] = useState(false);
function content() {
try {
const msg = JSON.parse(props.chat?.content as string);
return (
<div
className={styles.imgPreview}
style={{
display: 'flex',
color: '#000',
@@ -16,7 +20,13 @@ export const ChatImage: React.FC<IChatItem> = (props) => {
justifyContent: 'center',
}}
>
<img
<Image
preview={{
visible: visible,
onVisibleChange: (value, prevValue, currentIndex) => {
setVisible(value);
},
}}
src={`/api/${msg.path}`}
style={{ maxWidth: '100%', maxHeight: '100%', objectFit: 'contain' }}
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 styles from './index.module.scss';
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() {
try {
const msg = JSON.parse(props.chat?.content as string);
// setRecord(msg);
console.log(msg);
return (
<a
style={{ display: 'flex', color: '#000' }}
download={msg.file_name}
href={`/api/${msg.path}`}
<div
style={{
display: 'flex',
color: '#000',
flexDirection: 'column',
minWidth: 260,
cursor: 'pointer',
}}
onClick={() => {
setVisible(true);
}}
>
<div style={{ wordBreak: 'break-all', paddingRight: 8 }}>{msg.file_name}</div>
<FileTextFilled style={{ fontSize: 40, color: '#FA9D3B', flexShrink: 0 }} />
</a>
<div style={{ wordBreak: 'break-all', fontSize: 16 }}>{msg.title}</div>
{msg.item[0] ? (
<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) {
return (
<div>
<div style={{ wordBreak: 'break-all', paddingRight: 8 }}>{props.chat?.content}</div>
<FileTextFilled style={{ fontSize: 40, color: '#FA9D3B', flexShrink: 0 }} />
</div>
);
}
@@ -28,6 +81,32 @@ export const ChatRecord: React.FC<IChatItem> = (props) => {
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 ? (
<div className={styles.chatRight}>
<div className={styles.chatContentBox}>

View File

@@ -16,9 +16,9 @@ export const ChatTime: React.FC<IChatTimeProps> = (props) => {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const dayDiff =
(Date.parse(
(new Date(
`${now.getFullYear()}-${padWith(now.getMonth() + 1)}-${padWith(now.getDate())} 00:00:00`,
) -
).getTime() -
new Date(props.msgtime).getTime()) /
(24 * 60 * 60 * 1000) +
1;
@@ -58,7 +58,7 @@ export const ChatTime: React.FC<IChatTimeProps> = (props) => {
};
return (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: 12 }}>
<div
style={{
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 styles from './index.module.scss';
export const ChatVideo: React.FC<IChatItem> = (props) => {
const [open, setOpen] = useState(false);
const videoRef = useRef<any>();
function playLen(v: number) {
const len = Number(v) || 0;
if (len > 60) {
@@ -19,10 +24,58 @@ export const ChatVideo: React.FC<IChatItem> = (props) => {
const msg = JSON.parse(props.chat?.content as string);
// console.log(msg);
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 }} />
<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) {
return (

View File

@@ -1,14 +1,39 @@
import { FileTextFilled } from '@ant-design/icons';
import { useRef, useState } from 'react';
import { IChatItem } from '../ChatLogsType';
import styles from './index.module.scss';
export const ChatVoice: React.FC<IChatItem> = (props) => {
const [open, setOpen] = useState(false);
const audioRef = useRef<any>();
function content() {
try {
const msg = JSON.parse(props.chat?.content as string);
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' }}>
{msg.play_length}"
</span>
@@ -18,7 +43,14 @@ export const ChatVoice: React.FC<IChatItem> = (props) => {
fill="#000000"
></path>
</svg>
</a>
</div>
<audio
ref={audioRef}
src={`/api/${msg.path}`}
style={{ display: `${open ? 'block' : 'none'}` }}
controls
></audio>
</div>
);
} catch (_e) {
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 {
flex-shrink: 0;
width: 34px;
height: 34px;
@@ -109,3 +108,11 @@
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 (Array.isArray(res.data)) {
if (param.curr_page == 1) {
setChatLogs([...res.data, { curr_page: param.curr_page }]);
setChatLogs([...res.data.reverse(), { curr_page: param.curr_page }]);
} 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>
);
} else {
if (timeDiffRef.current == '') {
timeDiffRef.current = item.msg_time;
timeShowRef.current = false;
} else {
if (
Date.parse(item.msg_time) - Date.parse(timeDiffRef.current) >
5 * 60 * 1000
) {
timeDiffRef.current = item.msg_time;
timeShowRef.current = true;
} else {
timeShowRef.current = false;
}
}
// if (timeDiffRef.current == '') {
// timeDiffRef.current = item.msg_time;
// timeShowRef.current = false;
// } else {
// if (
// new Date(item.msg_time).getTime() - new Date(timeDiffRef.current).getTime() >
// 5 * 60 * 1000
// ) {
// timeDiffRef.current = item.msg_time;
// timeShowRef.current = true;
// } else {
// timeShowRef.current = false;
// }
// }
if (i == 0) {
timeShowRef.current = true;
}
// if (i == 0) {
// timeShowRef.current = true;
// }
return (
<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>
</div>
);

View File

@@ -35,3 +35,14 @@ export const notificationError = (message: IError) => {
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')}`;
}
};