feat: first commit

This commit is contained in:
eleanor.mao
2025-03-31 22:34:22 +08:00
commit d25187c9c8
390 changed files with 57031 additions and 0 deletions

View File

@ -0,0 +1,54 @@
@import '@/styles/common.less';
@import '@/styles/variables.less';
.base-message {
width: 100%;
.flex-row();
align-items: flex-start;
margin-top: 40px;
padding: 0 32px;
box-sizing: border-box;
&.is-sender {
flex-direction: row-reverse;
}
&__avatar {
width: 80px;
height: 80px;
border-radius: 50%;
}
&__content-container {
flex: 1;
.flex-column();
align-items: flex-start;
margin: 0 16px;
.is-sender & {
align-items: flex-end;
}
}
&__content {
font-size: 28px;
line-height: 40px;
font-weight: 400;
color: #1D2129;
background: #D9D9D9;
padding: 20px 24px;
border-radius: 20px;
}
&__status {
font-size: 20px;
line-height: 28px;
font-weight: 400;
color: @blHighlightColor;
margin-top: 8px;
&.done {
color: #8D8E99;
}
}
}

View File

@ -0,0 +1,66 @@
import { Image } from '@tarojs/components';
import classNames from 'classnames';
import { PropsWithChildren, useEffect, useState, useCallback } from 'react';
import { MaterialViewSource } from '@/constants/material';
import useUserInfo from '@/hooks/use-user-info';
import { IChatMessage } from '@/types/message';
import { getScrollItemId } from '@/utils/common';
import { navigateTo } from '@/utils/route';
import { PageUrl } from '@/constants/app';
import './index.less';
export interface IBaseMessageProps {
id: string;
message: IChatMessage;
}
export interface IUserMessageProps extends PropsWithChildren, IBaseMessageProps {
isRead?: boolean;
}
const PREFIX = 'base-message';
function BaseMessage(props: IUserMessageProps) {
const { id, message, isRead: isReadProps, children } = props;
const { userId } = useUserInfo();
const [isRead, setIsRead] = useState(message.isRead);
const isSender = message.senderUserId === userId;
// useEffect(() => {
// if (isSender) {
// return;
// }
// // 对方发的消息,拉取到消息后,后端会主动已读,这里延迟模拟下
// const timer = setTimeout(() => setIsRead(true), 1200);
// return () => clearTimeout(timer);
// }, [isSender]);
const handleClick = useCallback(
() => navigateTo(PageUrl.MaterialView, { resumeId: message.jobId, source: MaterialViewSource.Chat }),
[message.jobId]
);
useEffect(() => {
if (isRead) {
return;
}
isReadProps && setIsRead(true);
}, [isRead, isReadProps]);
return (
<div className={classNames(PREFIX, { 'is-sender': isSender })} id={getScrollItemId(id)}>
<Image
mode="aspectFit"
className={`${PREFIX}__avatar`}
src={message.senderAvatarUrl || require('@/statics/png/default_avatar.png')}
/>
<div className={`${PREFIX}__content-container`}>
{children}
<div className={classNames(`${PREFIX}__status`, { done: isRead })}>{isRead ? '已读' : '未读'}</div>
</div>
</div>
);
}
export default BaseMessage;

View File

@ -0,0 +1,50 @@
@import '@/styles/common.less';
@import '@/styles/variables.less';
.exchange-message {
width: 100%;
.flex-column();
margin-top: 40px;
&__content {
padding: 24px 60px;
background: #FFFFFF;
border-radius: 20px;
}
&__title {
font-size: 28px;
line-height: 40px;
font-weight: 400;
color: @blColor;
}
&__buttons {
.flex-row();
justify-content: center;
margin-top: 32px;
}
&__reject {
.button(@width: 176px; @height: 56px; @fontSize: 28px; @borderRadius: 48px);
border: 2px solid #E0E0E0;
color: @blColor;
background: #FFF;
margin-right: 24px;
}
&__agree {
.button(@width: 176px; @height: 56px; @fontSize: 28px; @borderRadius: 48px);
}
&__disable-btn {
.button(@height: 56px; @fontSize: 28px; @borderRadius: 48px);
padding: 0 74px;
color: #C0C0C0;
background: #F0F0F0;
&:active {
background: #F0F0F0;
}
}
}

View File

@ -0,0 +1,91 @@
import { Button } from '@tarojs/components';
import { useCallback, useEffect, useState } from 'react';
import { IBaseMessageProps } from '@/components/message-chat/base';
import { MessageActionStatus } from '@/constants/message';
import useUserInfo from '@/hooks/use-user-info';
import { getScrollItemId } from '@/utils/common';
import { posConfirmAction, postAddMessageTimes, requestChatActionStatus } from '@/utils/message';
import './index.less';
interface IProps extends IBaseMessageProps {
onClick: () => void;
}
const PREFIX = 'exchange-message';
function ContactMessage(props: IProps) {
const { id, message, onClick } = props;
const { userId } = useUserInfo();
const [status, setStatus] = useState<MessageActionStatus>();
const isSender = message.senderUserId === userId;
const handleClickReject = useCallback(async () => {
if (isSender || status !== MessageActionStatus.Send) {
return;
}
postAddMessageTimes('click_reject_exchange_contact');
await posConfirmAction({ actionId: message.actionId, status: false });
setStatus(MessageActionStatus.Reject);
onClick();
}, [isSender, status, message.actionId, onClick]);
const handleClickAgree = useCallback(async () => {
if (isSender || status !== MessageActionStatus.Send) {
return;
}
postAddMessageTimes('click_agree_exchange_contact');
await posConfirmAction({ actionId: message.actionId, status: true });
setStatus(MessageActionStatus.Agree);
onClick();
}, [isSender, status, message.actionId, onClick]);
useEffect(() => {
const init = async () => {
const res = await requestChatActionStatus(message.actionId);
setStatus(res);
};
init();
}, [message.actionId]);
return (
<div className={PREFIX} id={getScrollItemId(id)}>
<div className={`${PREFIX}__content`}>
<div className={`${PREFIX}__title`}>
{isSender ? '您已向对方请求交换联系方式' : `${message.senderName}申请交换联系方式,沟通面试`}
</div>
<div className={`${PREFIX}__buttons`}>
{isSender && (
<Button className={`${PREFIX}__disable-btn`} onClick={handleClickAgree}>
{status === MessageActionStatus.Agree
? '对方已同意'
: status === MessageActionStatus.Reject
? '对方已拒绝'
: '等待对方同意'}
</Button>
)}
{!isSender && (
<>
{(status === MessageActionStatus.Send || !status) && (
<>
<Button className={`${PREFIX}__reject`} onClick={handleClickReject}>
</Button>
<Button className={`${PREFIX}__agree`} onClick={handleClickAgree}>
</Button>
</>
)}
{status === MessageActionStatus.Agree && <Button className={`${PREFIX}__disable-btn`}></Button>}
{status === MessageActionStatus.Reject && <Button className={`${PREFIX}__disable-btn`}></Button>}
</>
)}
</div>
</div>
</div>
);
}
export default ContactMessage;

View File

@ -0,0 +1,8 @@
import ContactMessage from './contact';
import JobMessage from './job';
import LocationMessage from './location';
import MaterialMessage from './material';
import TextMessage from './text';
import TimeMessage from './time';
export { ContactMessage, JobMessage, LocationMessage, MaterialMessage, TextMessage, TimeMessage };

View File

@ -0,0 +1,29 @@
@import '@/styles/common.less';
@import '@/styles/variables.less';
.job-message {
width: calc(100% - 64px);
.flex-column();
align-items: flex-start;
margin: 40px 32px 0;
padding: 20px 24px;
background: #FFFFFF;
border-radius: 20px;
box-sizing: border-box;
&__title {
font-size: 28px;
line-height: 40px;
font-weight: 500;
color: @blColor;
}
&__salary {
font-size: 24px;
line-height: 36px;
font-weight: 400;
color: @blColorG2;
margin-top: 8px;
}
}

View File

@ -0,0 +1,38 @@
import { useCallback } from 'react';
import { IBaseMessageProps } from '@/components/message-chat/base';
import { PageUrl } from '@/constants/app';
import { IJobMessage } from '@/types/message';
import { getScrollItemId, safeJsonParse } from '@/utils/common';
import { getJobSalary } from '@/utils/job';
import { navigateTo } from '@/utils/route';
import './index.less';
interface IProps extends IBaseMessageProps {}
const PREFIX = 'job-message';
function JobMessage(props: IProps) {
const { id, message } = props;
const data = safeJsonParse<IJobMessage>(message.actionObject, null);
const handleClick = useCallback(() => {
if (!data) {
return;
}
navigateTo(PageUrl.JobDetail, { id: data.id });
}, [data]);
if (!data) {
return null;
}
return (
<div className={PREFIX} id={getScrollItemId(id)} onClick={handleClick}>
<div className={`${PREFIX}__title`}>{data.title}</div>
<div className={`${PREFIX}__salary`}>{getJobSalary(data)}</div>
</div>
);
}
export default JobMessage;

View File

@ -0,0 +1,45 @@
@import '@/styles/common.less';
@import '@/styles/variables.less';
.location-message {
&__map-container {
width: 440px;
.flex-column();
align-items: flex-start;
background: #FFF;
border-radius: 20px;
position: relative;
}
&__name {
font-size: 28px;
line-height: 40px;
font-weight: 400;
color: @blColor;
margin: 20px 20px 0;
.noWrap();
}
&__address {
font-size: 24px;
line-height: 36px;
font-weight: 400;
color: @blColorG1;
margin: 4px 20px 20px;
.noWrap();
}
&__map {
width: 100%;
height: 160px;
}
&__mask {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
}

View File

@ -0,0 +1,69 @@
import { Map } from '@tarojs/components';
import Taro from '@tarojs/taro';
import { useCallback } from 'react';
import BaseMessage, { IUserMessageProps } from '@/components/message-chat/base';
import { ILocationMessage } from '@/types/message';
import { safeJsonParse } from '@/utils/common';
import Toast from '@/utils/toast';
import './index.less';
interface IProps extends IUserMessageProps {}
const PREFIX = 'location-message';
function LocationMessage(props: IProps) {
const { message } = props;
const data = safeJsonParse<ILocationMessage>(message.actionObject, null);
const handleClickMap = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
Taro.openLocation({
name: data.name,
address: data.address,
longitude: Number(data.longitude),
latitude: Number(data.latitude),
scale: 18,
});
},
[data]
);
if (!data) {
return null;
}
return (
<BaseMessage {...props}>
<div className={`${PREFIX}__map-container`} onClick={handleClickMap}>
<div className={`${PREFIX}__name`}>{data.name}</div>
<div className={`${PREFIX}__address`}>{data.address}</div>
<Map
scale={15}
enableZoom={false}
enableScroll={false}
className={`${PREFIX}__map`}
latitude={Number(data.latitude)}
longitude={Number(data.longitude)}
markers={[
{
id: 0,
latitude: Number(data.latitude),
longitude: Number(data.longitude),
iconPath: '',
width: 20,
height: 28,
},
]}
onError={() => Toast.error('地图加载错误')}
/>
<div className={`${PREFIX}__mask`} onClick={handleClickMap} />
</div>
</BaseMessage>
);
}
export default LocationMessage;

View File

@ -0,0 +1,30 @@
@import '@/styles/common.less';
@import '@/styles/variables.less';
.material-message {
width: calc(100% - 64px);
.flex-column();
align-items: flex-start;
margin: 40px 32px 0;
padding: 20px 24px;
background: #FFFFFF;
border-radius: 20px;
box-sizing: border-box;
&__name {
font-size: 28px;
line-height: 40px;
font-weight: 500;
color: @blColor;
}
&__basic,
&__categories {
font-size: 24px;
line-height: 36px;
font-weight: 400;
color: @blColorG2;
margin-top: 8px;
}
}

View File

@ -0,0 +1,41 @@
import { useCallback } from 'react';
import { IBaseMessageProps } from '@/components/message-chat/base';
import { PageUrl } from '@/constants/app';
import { MaterialViewSource } from '@/constants/material';
import { IMaterialMessage } from '@/types/message';
import { getScrollItemId, safeJsonParse } from '@/utils/common';
import { getBasicInfo } from '@/utils/material';
import { navigateTo } from '@/utils/route';
import './index.less';
interface IProps extends IBaseMessageProps {}
const PREFIX = 'material-message';
function MaterialMessage(props: IProps) {
const { id, message } = props;
const data = safeJsonParse<IMaterialMessage>(message.actionObject, null);
const handleClick = useCallback(() => {
if (!data) {
return;
}
navigateTo(PageUrl.MaterialView, { resumeId: data.id, source: MaterialViewSource.Chat });
}, [data]);
if (!data) {
return null;
}
return (
<div className={PREFIX} id={getScrollItemId(id)} onClick={handleClick}>
<div className={`${PREFIX}__name`}>{data.name}</div>
<div className={`${PREFIX}__basic`}>{getBasicInfo(data)}</div>
<div className={`${PREFIX}__categories`}>{`播过 ${data.workedSecCategoryStr}`}</div>
</div>
);
}
export default MaterialMessage;

View File

@ -0,0 +1,14 @@
@import '@/styles/common.less';
@import '@/styles/variables.less';
.text-message {
&__content {
font-size: 28px;
line-height: 40px;
font-weight: 400;
color: #1D2129;
background: #D9D9D9;
padding: 20px 24px;
border-radius: 20px;
}
}

View File

@ -0,0 +1,32 @@
import { Text } from '@tarojs/components';
import { useCallback } from 'react';
import BaseMessage, { IUserMessageProps } from '@/components/message-chat/base';
import { copy } from '@/utils/common';
import Toast from '@/utils/toast';
import './index.less';
interface IProps extends IUserMessageProps {}
const PREFIX = 'text-message';
function TextMessage(props: IProps) {
const { message } = props;
const handleLongPress = useCallback(async () => {
await copy(message.content);
Toast.success('复制成功');
}, [message.content]);
return (
<BaseMessage {...props}>
<Text className={`${PREFIX}__content`} onLongPress={handleLongPress}>
{message.content}
</Text>
</BaseMessage>
);
}
export default TextMessage;

View File

@ -0,0 +1,12 @@
@import '@/styles/common.less';
@import '@/styles/variables.less';
.time-message {
width: 100%;
font-size: 28px;
line-height: 40px;
font-weight: 400;
text-align: center;
color: #8D8E99;
margin-top: 40px;
}

View File

@ -0,0 +1,20 @@
import { IBaseMessageProps } from '@/components/message-chat/base';
import { getScrollItemId } from '@/utils/common';
import { formatTime } from '@/utils/time';
import './index.less';
interface IProps extends IBaseMessageProps {}
const PREFIX = 'time-message';
function TimeMessage(props: IProps) {
const { id, message } = props;
return (
<div className={PREFIX} id={getScrollItemId(id)}>
{formatTime(message.content, 'MM-DD HH:mm')}
</div>
);
}
export default TimeMessage;