Files
boluo-app-main/src/fragments/profile/view/index.tsx
2025-07-02 23:36:34 +08:00

262 lines
9.6 KiB
TypeScript

import { BaseEventOrig, Image, Swiper, SwiperItem, SwiperProps } from '@tarojs/components';
import Taro from '@tarojs/taro';
import classNames from 'classnames';
import { useCallback, useState } from 'react';
import DevDiv from '@/components/dev-div';
import SafeBottomPadding from '@/components/safe-bottom-padding';
import { OpenSource, PageUrl } from '@/constants/app';
import { CITY_CODE_TO_NAME_MAP } from '@/constants/city';
import { FULL_PRICE_OPTIONS, PART_PRICE_OPTIONS } from '@/constants/job';
import { ProfileGroupType, ProfileTitleMap, WORK_YEAR_OPTIONS } from '@/constants/material';
import { MaterialProfile } from '@/types/material';
import { logWithPrefix, isDesktop } from '@/utils/common';
import { getBasicInfo, sortVideos } from '@/utils/material';
import { navigateTo } from '@/utils/route';
import './index.less';
import onChangeEventDetail = SwiperProps.onChangeEventDetail;
import { JoinGroupHint } from '@/components/join-group-hint';
interface IProps {
editable: boolean;
profile: MaterialProfile;
onDev?: () => void;
}
const PREFIX = 'profile-view-fragment';
const log = logWithPrefix(PREFIX);
const DEFAULT_TEXT = '未填写';
const getIndentCity = (codeString: string = '') => {
const codes = codeString.split('、');
const cityNames: string[] = [];
codes.forEach(code => {
const cityName = CITY_CODE_TO_NAME_MAP.get(code);
cityName && cityNames.push(cityName);
});
return cityNames.join('、');
};
const getIndentPrice = (min: number, max: number, full: boolean) => {
if (!min && !max) {
return DEFAULT_TEXT;
}
const options = full ? FULL_PRICE_OPTIONS : PART_PRICE_OPTIONS;
const option = options.find(o => o.value.minSalary === min && o.value.maxSalary === max);
if (option) {
return option.label;
}
const unit = full ? 'K' : '';
const prices: string[] = [];
min && prices.push(`${full ? Number(min) / 1000 : min}${unit}`);
max && prices.push(`${full ? Number(max) / 1000 : max}${unit}`);
return prices.join(' - ');
};
const getWorkYear = (year: number) => {
if (!year) {
return DEFAULT_TEXT;
}
const y = Math.min(year, 3);
return WORK_YEAR_OPTIONS.find(option => option.value === y)?.label || `${y}`;
};
const getDataGroup = (profile: MaterialProfile | null) => {
if (!profile) {
return [];
}
return [
{
title: ProfileTitleMap[ProfileGroupType.Intention],
type: ProfileGroupType.Intention,
items: [
{ title: '全职薪资', value: getIndentPrice(profile.fullTimeMinPrice, profile.fullTimeMaxPrice, true) || '' },
{ title: '兼职薪资', value: getIndentPrice(profile.partyTimeMinPrice, profile.partyTimeMaxPrice, false) || '' },
{ title: '意向城市', value: getIndentCity(profile.cityCodes) || DEFAULT_TEXT },
{ title: '是否接受坐班', value: profile.acceptWorkForSit ? '是' : '否' },
],
},
{
title: ProfileTitleMap[ProfileGroupType.Experience],
type: ProfileGroupType.Experience,
items: [
{ title: '直播年限', value: getWorkYear(profile.workedYear) || DEFAULT_TEXT },
{ title: '直播过的账号', value: profile.workedAccounts || DEFAULT_TEXT },
{ title: '自然流起号经验', value: Boolean(profile.newAccountExperience) ? '有' : '无' },
{ title: '直播过的品类', value: profile.workedSecCategoryStr || DEFAULT_TEXT },
{ title: '最高GMV', value: profile.maxGmv ? `${profile.maxGmv / 10000}` : DEFAULT_TEXT },
{ title: '最高在线人数', value: profile.maxOnline || DEFAULT_TEXT },
],
},
{
title: ProfileTitleMap[ProfileGroupType.Advantages],
type: ProfileGroupType.Advantages,
items: [{ title: '自身优势', value: profile.advantages || DEFAULT_TEXT, fullLine: true }],
},
];
};
export default function ProfileViewFragment(props: IProps) {
const { profile, editable, onDev } = props;
const [coverIndex, setCoverIndex] = useState(0);
const dataGroup = getDataGroup(profile);
const videos = sortVideos(profile?.materialVideoInfoList || []);
const handleClickVideo = useCallback(
(index: number) => {
log('handleClickVideo', index);
if (isDesktop) {
navigateTo(PageUrl.MaterialWebview, {
source: encodeURIComponent(videos[index].url),
});
} else {
Taro.previewMedia({
sources: videos.map(video => ({ url: video.url, type: video.type })),
current: index,
});
}
},
[videos]
);
const handleEditVideo = useCallback(() => {
log('handleEditBasic');
navigateTo(PageUrl.MaterialUploadVideo, { source: OpenSource.UserPage });
}, []);
const handleEditBasic = useCallback(() => {
log('handleEditBasic');
navigateTo(PageUrl.MaterialEditProfile, { type: ProfileGroupType.Basic });
}, []);
const handleEditGroupItem = useCallback((groupType: ProfileGroupType) => {
log('handleEditGroupItem', groupType);
navigateTo(PageUrl.MaterialEditProfile, { type: groupType });
}, []);
const videoLength = videos.length ? videos.length : 0;
const showSwiperButton = videoLength > 1 && isDesktop;
const handleChange = useCallback((e: BaseEventOrig<onChangeEventDetail>) => {
setCoverIndex(e.detail.current);
}, []);
const handlePrev = useCallback(() => {
setCoverIndex(coverIndex ? coverIndex - 1 : videoLength - 1);
}, [coverIndex, videoLength]);
const handleNext = useCallback(() => {
setCoverIndex(coverIndex === videoLength - 1 ? 0 : coverIndex + 1);
}, [coverIndex, videoLength]);
return (
<div className={PREFIX}>
<div className={`${PREFIX}__header`}>
<Swiper className={`${PREFIX}__header__swiper`} current={coverIndex} onChange={handleChange} circular>
{videos.map((cover, index) => (
<SwiperItem key={cover.coverUrl}>
<div className={`${PREFIX}__header__swiper-item`}>
<Image
mode="aspectFill"
src={cover.coverUrl}
className={`${PREFIX}__header__cover`}
onClick={() => handleClickVideo(index)}
/>
{cover.type === 'video' && (
<Image
className={`${PREFIX}__header__cover-preview`}
mode="aspectFit"
src={require('@/statics/svg/preview_video.svg')}
onClick={() => handleClickVideo(index)}
/>
)}
</div>
</SwiperItem>
))}
</Swiper>
{showSwiperButton && (
<div className={`${PREFIX}__header__swiper-button__prev`} onClick={handlePrev}>
<Image
className={`${PREFIX}__header__swiper-button__image`}
mode="aspectFit"
src={require('@/statics/svg/arrow-left-thin.svg')}
/>
</div>
)}
{showSwiperButton && (
<div className={`${PREFIX}__header__swiper-button__next`} onClick={handleNext}>
<Image
className={`${PREFIX}__header__swiper-button__image`}
mode="aspectFit"
src={require('@/statics/svg/arrow-right-thin.svg')}
/>
</div>
)}
{editable && (
<div className={`${PREFIX}__header__edit-video`} onClick={handleEditVideo}>
</div>
)}
<div className={`${PREFIX}__header__banner`}>
<div>{videos[coverIndex]?.title || ''}</div>
<div
className={`${PREFIX}__header__video-size`}
>{`${coverIndex + 1}/${profile.materialVideoInfoList.length}`}</div>
</div>
</div>
{!editable && <JoinGroupHint />}
<div className={`${PREFIX}__body`}>
<div className={`${PREFIX}__basic-info`}>
<div className={`${PREFIX}__basic-info__name-container`}>
<DevDiv className={`${PREFIX}__basic-info__name`} OnDev={onDev}>
{profile.name}
</DevDiv>
{editable && (
<div className={`${PREFIX}__basic-info__edit`} onClick={handleEditBasic}>
</div>
)}
</div>
<div className={`${PREFIX}__basic-info__content`}>{getBasicInfo(profile)}</div>
<div className={`${PREFIX}__divider`} />
</div>
{dataGroup.map((data, index: number) => (
<div className={`${PREFIX}__info-group`} key={data.type}>
<div className={`${PREFIX}__info-group__header`}>
{index ? (
<div className={`${PREFIX}__info-group__header__title`}>{data.title}</div>
) : (
<DevDiv className={`${PREFIX}__info-group__header__title`} OnDev={onDev}>
{data.title}
</DevDiv>
)}
{editable && (
<div className={`${PREFIX}__info-group__header__edit`} onClick={() => handleEditGroupItem(data.type)}>
</div>
)}
</div>
<div className={`${PREFIX}__info-group__items`}>
{data.items.map(item => (
<div
key={String(item.value)}
className={classNames(`${PREFIX}__info-group__item`, { 'full-line': item.fullLine })}
>
<div className={`${PREFIX}__info-group__item__title`}>{item.title}</div>
<div className={`${PREFIX}__info-group__item__value`}>{item.value}</div>
</div>
))}
</div>
{index !== dataGroup.length - 1 && <div className={`${PREFIX}__divider`} />}
</div>
))}
</div>
<SafeBottomPadding />
</div>
);
}