2 Commits

Author SHA1 Message Date
828e9b7512 feat: update style 2025-09-19 23:53:17 +08:00
1d8a0d7a33 feat: update 2025-09-19 23:49:31 +08:00
20 changed files with 197 additions and 390 deletions

View File

@ -5,34 +5,19 @@ import { isEqual } from 'lodash-es';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import PickerToolbar from '@/components/picker-toolbar'; import PickerToolbar from '@/components/picker-toolbar';
import { import { FULL_PRICE_OPTIONS, PART_PRICE_OPTIONS } from '@/constants/job';
EmployType, import { ALL_ANCHOR_READ_TYPES, ANCHOR_READ_TITLE_MAP, AnchorReadType } from '@/constants/material';
ALL_EMPLOY_TYPES,
FULL_PRICE_OPTIONS,
PART_PRICE_OPTIONS,
EMPLOY_TYPE_TITLE_MAP,
} from '@/constants/job';
import {
ALL_ANCHOR_READ_TYPES,
ALL_GENDER_TYPES,
ANCHOR_READ_TITLE_MAP,
AnchorReadType,
GENDER_TYPE_TITLE_MAP,
GenderType,
} from '@/constants/material';
import { IAnchorFilters } from '@/types/material'; import { IAnchorFilters } from '@/types/material';
import { isUndefined } from '@/utils/common';
import './index.less'; import './index.less';
type MoreFilter = Omit<IAnchorFilters, 'employType' | 'gender'>;
interface IProps { interface IProps {
value: IAnchorFilters; value: MoreFilter;
onConfirm: (newValue: IAnchorFilters) => void; onConfirm: (newValue: MoreFilter) => void;
} }
const PREFIX = 'anchor-picker'; const PREFIX = 'anchor-picker';
const getDefaultGender = (value: IAnchorFilters) => value.gender;
const getDefaultEmploy = (value: IAnchorFilters) => value.employType;
const getDefaultReadType = (value: IAnchorFilters) => value.readType; const getDefaultReadType = (value: IAnchorFilters) => value.readType;
const getDefaultCategory = (value: IAnchorFilters) => value.category || ''; const getDefaultCategory = (value: IAnchorFilters) => value.category || '';
const getSalaryValue = (value: IAnchorFilters, full: boolean) => { const getSalaryValue = (value: IAnchorFilters, full: boolean) => {
@ -47,9 +32,7 @@ const getSalaryValue = (value: IAnchorFilters, full: boolean) => {
function AnchorPicker(props: IProps) { function AnchorPicker(props: IProps) {
const { value, onConfirm } = props; const { value, onConfirm } = props;
const [gender, setGender] = useState<GenderType | undefined>(getDefaultGender(value));
const [readType, setReadType] = useState<AnchorReadType | undefined>(getDefaultReadType(value)); const [readType, setReadType] = useState<AnchorReadType | undefined>(getDefaultReadType(value));
const [employType, setEmployType] = useState<EmployType | undefined>(getDefaultEmploy(value));
const [fullSalary, setFullSalary] = useState(getSalaryValue(value, true)); const [fullSalary, setFullSalary] = useState(getSalaryValue(value, true));
const [partSalary, setPartSalary] = useState(getSalaryValue(value, false)); const [partSalary, setPartSalary] = useState(getSalaryValue(value, false));
const [category, setCategory] = useState(getDefaultCategory(value)); const [category, setCategory] = useState(getDefaultCategory(value));
@ -59,9 +42,7 @@ function AnchorPicker(props: IProps) {
}, []); }, []);
const handleClickReset = useCallback(() => { const handleClickReset = useCallback(() => {
setGender(undefined);
setReadType(undefined); setReadType(undefined);
setEmployType(undefined);
setFullSalary(null); setFullSalary(null);
setPartSalary(null); setPartSalary(null);
setCategory(''); setCategory('');
@ -83,10 +64,6 @@ function AnchorPicker(props: IProps) {
const handleClickConfirm = useCallback(() => { const handleClickConfirm = useCallback(() => {
const filters: IAnchorFilters = {}; const filters: IAnchorFilters = {};
if (!isUndefined(gender)) {
filters.gender = gender === GenderType.All ? undefined : gender;
}
employType && (filters.employType = employType);
readType && (filters.readType = readType); readType && (filters.readType = readType);
category && (filters.category = category); category && (filters.category = category);
if (fullSalary) { if (fullSalary) {
@ -98,34 +75,10 @@ function AnchorPicker(props: IProps) {
filters.highPriceForPartyTime = partSalary.maxSalary; filters.highPriceForPartyTime = partSalary.maxSalary;
} }
onConfirm(filters); onConfirm(filters);
}, [gender, employType, readType, category, fullSalary, partSalary, onConfirm]); }, [readType, category, fullSalary, partSalary, onConfirm]);
return ( return (
<div className={PREFIX}> <div className={PREFIX}>
<div className={`${PREFIX}__title`}></div>
<div className={`${PREFIX}__container`}>
{ALL_GENDER_TYPES.map((type: GenderType) => (
<div
key={type}
onClick={() => setGender(type)}
className={classNames(`${PREFIX}__item`, { selected: type === gender })}
>
{GENDER_TYPE_TITLE_MAP[type]}
</div>
))}
</div>
<div className={`${PREFIX}__title`}>/</div>
<div className={`${PREFIX}__container`}>
{ALL_EMPLOY_TYPES.map(type => (
<div
key={type}
onClick={() => setEmployType(type)}
className={classNames(`${PREFIX}__item`, { selected: type === employType })}
>
{EMPLOY_TYPE_TITLE_MAP[type]}
</div>
))}
</div>
<div className={`${PREFIX}__title`}>/</div> <div className={`${PREFIX}__title`}>/</div>
<div className={`${PREFIX}__container`}> <div className={`${PREFIX}__container`}>
{ALL_ANCHOR_READ_TYPES.map(type => ( {ALL_ANCHOR_READ_TYPES.map(type => (

View File

@ -0,0 +1,13 @@
import Select, { ISelectProps } from '@/components/select';
import { GENDER_OPTIONS, GenderType } from '@/constants/material';
interface IProps extends Omit<ISelectProps<GenderType>, 'options'> {
value: GenderType;
}
function GenderSelect(props: IProps) {
const { value: selectValue, onSelect } = props;
return <Select options={GENDER_OPTIONS} title="性别" value={selectValue} onSelect={onSelect} />;
}
export default GenderSelect;

View File

@ -61,7 +61,7 @@ export default function PartnerBanner() {
<div className={`${PREFIX}__close`} onClick={handlePartnerBannerClose} /> <div className={`${PREFIX}__close`} onClick={handlePartnerBannerClose} />
</div> </div>
{visible && ( {visible && (
<LoginDialog onCancel={() => setVisible(false)} onSuccess={handleBind} needPhone={needPhone} /> <LoginDialog disableCheck onCancel={() => setVisible(false)} onSuccess={handleBind} needPhone={needPhone} />
)} )}
</> </>
); );

View File

@ -1,19 +1,19 @@
import { Button, Canvas } from '@tarojs/components'; import { Button, Canvas } from '@tarojs/components';
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
import { useCallback } from 'react'; import { useCallback, useState } from 'react';
import { openCustomerServiceChat } from '@/utils/common'; import { openCustomerServiceChat } from '@/utils/common';
import { getCouponQrCode, generateMembershipCoupon } from '@/utils/coupon'; import { getPartnerQrcode } from '@/utils/partner';
import './index.less'; import './index.less';
const PREFIX = 'partner-intro'; const PREFIX = 'partner-intro';
export default function PartnerIntro() { export default function PartnerIntro() {
const [posterPath, setPosterPath] = useState('');
const getQrcode = async () => { const getQrcode = async () => {
try { try {
const { code } = await generateMembershipCoupon(); const data = await getPartnerQrcode(); // 假设 getPartnerQrcode 返回 ArrayBuffer
const data = await getCouponQrCode(code);
const base64 = Taro.arrayBufferToBase64(data); const base64 = Taro.arrayBufferToBase64(data);
return `data:image/png;base64,${base64}`; return `data:image/png;base64,${base64}`;
} catch (error) { } catch (error) {
@ -36,9 +36,11 @@ export default function PartnerIntro() {
height: 2668, // 实际绘制高度 height: 2668, // 实际绘制高度
destWidth: 750, // 目标显示宽度 destWidth: 750, // 目标显示宽度
destHeight: 1334, // 目标显示高度 destHeight: 1334, // 目标显示高度
fileType: 'png', fileType: 'jpg',
}); });
setPosterPath(tempFilePath.tempFilePath);
resolve(tempFilePath.tempFilePath); resolve(tempFilePath.tempFilePath);
}); });
} catch (error) { } catch (error) {
@ -55,21 +57,21 @@ export default function PartnerIntro() {
const canvas = res[0].node; const canvas = res[0].node;
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
canvas.width = 550 * 2; canvas.width = 1500;
canvas.height = 918 * 2; canvas.height = 2668;
ctx.scale(2, 2); ctx.scale(2, 2);
// 绘制背景图片 // 绘制背景图片
const bgImage = canvas.createImage(); const bgImage = canvas.createImage();
const poster = 'https://publiccdn.neighbourhood.com.cn/img/share-coupon-poster.png'; const poster = 'https://publiccdn.neighbourhood.com.cn/img/poster.png'
bgImage.src = poster; bgImage.src = poster;
bgImage.onload = () => { bgImage.onload = () => {
ctx.drawImage(bgImage, 0, 0, 550, 918); ctx.drawImage(bgImage, 0, 0, 750, 1334);
const qrCodeImage = canvas.createImage(); const qrCodeImage = canvas.createImage();
qrCodeImage.src = qrCode; // 假设 getQrcode() 返回的是二维码图片的路径 qrCodeImage.src = qrCode; // 假设 getQrcode() 返回的是二维码图片的路径
qrCodeImage.onload = () => { qrCodeImage.onload = () => {
ctx.drawImage(qrCodeImage, 196, 600, 160, 160); // 绘制二维码,位置和大小 ctx.drawImage(qrCodeImage, 235, 894, 280, 280); // 绘制二维码,位置和大小
saveCanvasToTempFile().then(tempPath => { saveCanvasToTempFile().then(tempPath => {
resolve(tempPath); resolve(tempPath);
}); });
@ -82,10 +84,13 @@ export default function PartnerIntro() {
}); });
}; };
const savePoster = async () => { const savePoster = async () => {
let filePath = posterPath;
if (!filePath) {
Taro.showLoading({ title: '正在生成海报' }); Taro.showLoading({ title: '正在生成海报' });
const qrCode = await getQrcode(); const qrCode = await getQrcode();
const filePath = await drawCanvas(qrCode); filePath = await drawCanvas(qrCode);
Taro.hideLoading(); Taro.hideLoading();
}
const res = await Taro.getSetting(); const res = await Taro.getSetting();
const hasPermission = res.authSetting['scope.writePhotosAlbum']; const hasPermission = res.authSetting['scope.writePhotosAlbum'];
@ -139,9 +144,7 @@ export default function PartnerIntro() {
<div className={`${PREFIX}__block`}> <div className={`${PREFIX}__block`}>
<div className={`${PREFIX}__title`}></div> <div className={`${PREFIX}__title`}></div>
<div className={`${PREFIX}__card`}> <div className={`${PREFIX}__card`}>
<div className={`${PREFIX}__body`}> <div className={`${PREFIX}__body`}></div>
</div>
</div> </div>
</div> </div>
<div className={`${PREFIX}__block`}> <div className={`${PREFIX}__block`}>
@ -160,10 +163,10 @@ export default function PartnerIntro() {
<div className={`${PREFIX}__footer`}> <div className={`${PREFIX}__footer`}>
<Button className={`${PREFIX}__download-button`} onClick={savePoster}> <Button className={`${PREFIX}__download-button`} onClick={savePoster}>
</Button> </Button>
<Button className={`${PREFIX}__share-button`} openType="share"> <Button className={`${PREFIX}__share-button`} openType="share">
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -28,6 +28,7 @@
justify-content: space-between; justify-content: space-between;
box-sizing: border-box; box-sizing: border-box;
padding: 0 24px; padding: 0 24px;
flex: 0 0 96px;
&.selected { &.selected {
color: @blHighlightColor; color: @blHighlightColor;

View File

@ -0,0 +1,13 @@
import Select, { ISelectProps } from '@/components/select';
import { JOB_TYPE_SELECT_OPTIONS_WITH_ALL, JobType } from '@/constants/job';
interface IProps extends Omit<ISelectProps<JobType>, 'options'> {
value: JobType;
}
function TopCategorySelect(props: IProps) {
const { value: selectValue, onSelect } = props;
return <Select options={JOB_TYPE_SELECT_OPTIONS_WITH_ALL} title="品类" value={selectValue} onSelect={onSelect} />;
}
export default TopCategorySelect;

View File

@ -7,6 +7,7 @@ export enum JobType {
Jewelry = 'JEWELRY', // 珠宝 Jewelry = 'JEWELRY', // 珠宝
Appliance = 'APPLIANCE', // 家电 Appliance = 'APPLIANCE', // 家电
Furniture = 'FURNITURE', // 日用家具 Furniture = 'FURNITURE', // 日用家具
OutdoorSports = 'OUTDOOR_SPORTS',
PetFamily = 'PET_FAMILY', // 母婴宠物 PetFamily = 'PET_FAMILY', // 母婴宠物
Luxury = 'LUXURY', // 奢品 Luxury = 'LUXURY', // 奢品
LocalLive = 'LOCAL_LIVE', // 本地生活 LocalLive = 'LOCAL_LIVE', // 本地生活
@ -87,6 +88,7 @@ export const JOB_TYPE_TITLE_MAP: { [key in JobType]: string } = {
[JobType.Jewelry]: '珠宝', [JobType.Jewelry]: '珠宝',
[JobType.Appliance]: '家电', [JobType.Appliance]: '家电',
[JobType.Furniture]: '日用家具', [JobType.Furniture]: '日用家具',
[JobType.OutdoorSports]: '户外运动',
[JobType.PetFamily]: '母婴宠物', [JobType.PetFamily]: '母婴宠物',
[JobType.Luxury]: '奢品', [JobType.Luxury]: '奢品',
[JobType.LocalLive]: '本地生活', [JobType.LocalLive]: '本地生活',
@ -173,6 +175,7 @@ export const JOB_TYPE_SELECT_OPTIONS = [
{ label: JOB_TYPE_TITLE_MAP[JobType.Jewelry], value: JobType.Jewelry }, { label: JOB_TYPE_TITLE_MAP[JobType.Jewelry], value: JobType.Jewelry },
{ label: JOB_TYPE_TITLE_MAP[JobType.Appliance], value: JobType.Appliance }, { label: JOB_TYPE_TITLE_MAP[JobType.Appliance], value: JobType.Appliance },
{ label: JOB_TYPE_TITLE_MAP[JobType.Furniture], value: JobType.Furniture }, { label: JOB_TYPE_TITLE_MAP[JobType.Furniture], value: JobType.Furniture },
{ label: JOB_TYPE_TITLE_MAP[JobType.OutdoorSports], value: JobType.OutdoorSports },
{ label: JOB_TYPE_TITLE_MAP[JobType.PetFamily], value: JobType.PetFamily }, { label: JOB_TYPE_TITLE_MAP[JobType.PetFamily], value: JobType.PetFamily },
{ label: JOB_TYPE_TITLE_MAP[JobType.Luxury], value: JobType.Luxury }, { label: JOB_TYPE_TITLE_MAP[JobType.Luxury], value: JobType.Luxury },
{ label: JOB_TYPE_TITLE_MAP[JobType.LocalLive], value: JobType.LocalLive }, { label: JOB_TYPE_TITLE_MAP[JobType.LocalLive], value: JobType.LocalLive },
@ -181,6 +184,11 @@ export const JOB_TYPE_SELECT_OPTIONS = [
{ label: JOB_TYPE_TITLE_MAP[JobType.Other], value: JobType.Other }, { label: JOB_TYPE_TITLE_MAP[JobType.Other], value: JobType.Other },
]; ];
export const JOB_TYPE_SELECT_OPTIONS_WITH_ALL = [
{ label: JOB_TYPE_TITLE_MAP[JobType.All], value: JobType.All },
...JOB_TYPE_SELECT_OPTIONS,
];
const MAX_SALARY = 10000000; const MAX_SALARY = 10000000;
export const PART_EMPLOY_SALARY_OPTIONS = [ export const PART_EMPLOY_SALARY_OPTIONS = [
{ label: '不限' }, { label: '不限' },

View File

@ -84,14 +84,25 @@ export const WORK_YEAR_OPTIONS = [
{ label: WORK_YEAR_LABELS[WorkedYears.MoreThreeYear], value: WorkedYears.MoreThreeYear }, { label: WORK_YEAR_LABELS[WorkedYears.MoreThreeYear], value: WorkedYears.MoreThreeYear },
]; ];
export const ALL_GENDER_TYPES = [GenderType.All, GenderType.MEN, GenderType.WOMEN];
export const GENDER_TYPE_TITLE_MAP = { export const GENDER_TYPE_TITLE_MAP = {
[GenderType.All]: '不限', [GenderType.All]: '不限',
[GenderType.WOMEN]: '女', [GenderType.WOMEN]: '女',
[GenderType.MEN]: '男', [GenderType.MEN]: '男',
}; };
export const GENDER_OPTIONS = [
{
value: GenderType.All,
label: GENDER_TYPE_TITLE_MAP[GenderType.All],
},
{
value: GenderType.WOMEN,
label: GENDER_TYPE_TITLE_MAP[GenderType.WOMEN],
},
{
value: GenderType.MEN,
label: GENDER_TYPE_TITLE_MAP[GenderType.MEN],
},
];
export const ALL_ANCHOR_READ_TYPES = Object.values(AnchorReadType); export const ALL_ANCHOR_READ_TYPES = Object.values(AnchorReadType);
export const ANCHOR_READ_TITLE_MAP = { export const ANCHOR_READ_TITLE_MAP = {

View File

@ -1,4 +1,4 @@
// export const DOMAIN = 'http://192.168.60.116:8082'; // export const DOMAIN = 'http://192.168.60.191:8082';
export const DOMAIN = process.env.BRAND === 'dev' ? 'https://dev.neighbourhood.cn' : 'https://neighbourhood.cn'; export const DOMAIN = process.env.BRAND === 'dev' ? 'https://dev.neighbourhood.cn' : 'https://neighbourhood.cn';
// export const DOMAIN = 'https://dev.neighbourhood.cn'; // export const DOMAIN = 'https://dev.neighbourhood.cn';
export const BASE_URL = `${DOMAIN}/api`; export const BASE_URL = `${DOMAIN}/api`;
@ -85,7 +85,4 @@ export enum API {
GET_PROFIT_STAT = '/user/profits', GET_PROFIT_STAT = '/user/profits',
WITHDRAW = '/user/withdraw', WITHDRAW = '/user/withdraw',
GET_WITHDRAW_LIST = '/user/withdraw/records', GET_WITHDRAW_LIST = '/user/withdraw/records',
GENERATE_MEMBERSHIP_COUPON = '/coupon/membership/generate',
CLAIM_MEMBERSHIP_COUPON = '/coupon/membership/claim',
GET_VIP_QRCODE = '/user/getVipQrCode',
} }

View File

@ -52,7 +52,11 @@
font-size: 28px; font-size: 28px;
line-height: 32px; line-height: 32px;
color: @blColor; color: @blColor;
gap: 20px;
&-item {
display: flex;
}
.title { .title {
margin-right: 5px; margin-right: 5px;
} }
@ -61,11 +65,17 @@
padding: 0 24px; padding: 0 24px;
} }
&__overlay-outer { &__overlay-outer {
top: 82px; top: 72px;
} }
&__overlay-inner { &__overlay-inner {
width: 100%; width: 100%;
max-height: 100%;
overflow: hidden;
.bl-select__items-container {
max-height: calc(100vh - 610rpx);
overflow-y: auto;
}
} }
&__tips-container { &__tips-container {
@ -91,8 +101,6 @@
margin-top: 50px; margin-top: 50px;
} }
&__popup { &__popup {
&-content { &-content {
.flex-column(); .flex-column();

View File

@ -5,11 +5,13 @@ import { Popup } from '@taroify/core';
import { ArrowDown, ArrowUp } from '@taroify/icons'; import { ArrowDown, ArrowUp } from '@taroify/icons';
import classNames from 'classnames'; import classNames from 'classnames';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import AnchorList, { IAnchorListProps } from '@/components/anchor-list'; import AnchorList, { IAnchorListProps } from '@/components/anchor-list';
import AnchorPicker from '@/components/anchor-picker'; import AnchorPicker from '@/components/anchor-picker';
import CustomNavigationBar from '@/components/custom-navigation-bar'; import CustomNavigationBar from '@/components/custom-navigation-bar';
import EmployTypeSelect from '@/components/employ-type-select';
import GenderSelect from '@/components/gender-select';
import HomePage from '@/components/home-page'; import HomePage from '@/components/home-page';
import Overlay from '@/components/overlay'; import Overlay from '@/components/overlay';
import PageLoading from '@/components/page-loading'; import PageLoading from '@/components/page-loading';
@ -17,8 +19,8 @@ import PartnerBanner from '@/components/partner-banner';
import SafeBottomPadding from '@/components/safe-bottom-padding'; import SafeBottomPadding from '@/components/safe-bottom-padding';
import SwitchBar from '@/components/switch-bar'; import SwitchBar from '@/components/switch-bar';
import { APP_TAB_BAR_ID, EventName, OpenSource, PageType, PageUrl, RoleType } from '@/constants/app'; import { APP_TAB_BAR_ID, EventName, OpenSource, PageType, PageUrl, RoleType } from '@/constants/app';
import { EmployType } from '@/constants/job'; import { EmployType, JobType } from '@/constants/job';
import { ALL_ANCHOR_SORT_TYPES, ANCHOR_SORT_TYPE_TITLE_MAP, AnchorSortType } from '@/constants/material'; import { ALL_ANCHOR_SORT_TYPES, ANCHOR_SORT_TYPE_TITLE_MAP, AnchorSortType, GenderType } from '@/constants/material';
import useInviteCode from '@/hooks/use-invite-code'; import useInviteCode from '@/hooks/use-invite-code';
import useListHeight, { IUseListHeightProps } from '@/hooks/use-list-height'; import useListHeight, { IUseListHeightProps } from '@/hooks/use-list-height';
import useLocation from '@/hooks/use-location'; import useLocation from '@/hooks/use-location';
@ -36,6 +38,7 @@ import { getCommonShareMessage } from '@/utils/share';
import Toast from '@/utils/toast'; import Toast from '@/utils/toast';
import './index.less'; import './index.less';
import TopCategorySelect from '@/components/top-category-select';
const PREFIX = 'page-anchor'; const PREFIX = 'page-anchor';
const LIST_CONTAINER_CLASS = `${PREFIX}__list-container`; const LIST_CONTAINER_CLASS = `${PREFIX}__list-container`;
@ -78,12 +81,22 @@ function ListWrapper(props: IAnchorListProps) {
return <AnchorList listHeight={listHeight} {...props} onListEmpty={handleListEmpty} />; return <AnchorList listHeight={listHeight} {...props} onListEmpty={handleListEmpty} />;
} }
enum FilterType {
gender,
employType,
topCategory,
more,
}
export default function AnchorPage() { export default function AnchorPage() {
const location = useLocation(); const location = useLocation();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [selectJob, setSelectJob] = useState<JobManageInfo | undefined>(); const [selectJob, setSelectJob] = useState<JobManageInfo | undefined>();
const [filters, setFilters] = useState<IAnchorFilters>({ employType: EmployType.All }); const [moreFilters, setMoreFilters] = useState<IAnchorFilters>({});
const [showFilter, setShowFilter] = useState<boolean>(false); const [gender, setGender] = useState<GenderType>(GenderType.All);
const [employType, setEmployType] = useState<EmployType>(EmployType.All);
const [topCategory, setTopCategory] = useState<JobType>(JobType.All);
const [showFilter, setShowFilter] = useState<FilterType | null>(null);
const [sortType, setSortType] = useState<AnchorSortType>(AnchorSortType.Active); const [sortType, setSortType] = useState<AnchorSortType>(AnchorSortType.Active);
const [openPopup, setOpenPopup] = useState<boolean>(false); const [openPopup, setOpenPopup] = useState<boolean>(false);
const [coordinate, setCoordinate] = useState<Coordinate>({ const [coordinate, setCoordinate] = useState<Coordinate>({
@ -106,20 +119,35 @@ export default function AnchorPage() {
[selectJob] [selectJob]
); );
const handleClickSalarySelect = useCallback(() => { const handleClickFilterSelect = (type: FilterType) => () => {
setShowFilter(!showFilter); setShowFilter(type === showFilter ? null : type);
}, [showFilter]); };
const handleHideFilter = useCallback(() => setShowFilter(false), []); const handleHideFilter = useCallback(() => setShowFilter(null), []);
const handleFilterChange = useCallback( const handleFilterChange = useCallback(
(newFilters: IAnchorFilters) => { (newFilters: IAnchorFilters) => {
!isEqual(newFilters, filters) && setFilters(newFilters); !isEqual(newFilters, moreFilters) && setMoreFilters(newFilters);
setShowFilter(false); setShowFilter(null);
}, },
[filters] [moreFilters]
); );
const handleChangeSelectGender = useCallback((value: GenderType) => {
setGender(value);
setShowFilter(null);
}, []);
const handleChangeSelectEmployType = useCallback((value: EmployType) => {
setEmployType(value);
setShowFilter(null);
}, []);
const handleChangeSelectTopCategory = useCallback((value: JobType) => {
setTopCategory(value);
setShowFilter(null);
}, []);
const handleClickSortType = useCallback(async (type: AnchorSortType) => setSortType(type), []); const handleClickSortType = useCallback(async (type: AnchorSortType) => setSortType(type), []);
const handleJobChange = useCallback( const handleJobChange = useCallback(
@ -205,6 +233,15 @@ export default function AnchorPage() {
useDidShow(() => requestUnreadMessageCount()); useDidShow(() => requestUnreadMessageCount());
const filters = useMemo(
() => ({
...moreFilters,
topCategory,
gender: gender === -1 ? undefined : gender,
employType,
}),
[moreFilters, gender, employType, topCategory]
);
return ( return (
<HomePage type={PageType.Anchor}> <HomePage type={PageType.Anchor}>
{!!loading && <PageLoading className={`${PREFIX}__loading`} />} {!!loading && <PageLoading className={`${PREFIX}__loading`} />}
@ -224,9 +261,23 @@ export default function AnchorPage() {
</div> </div>
))} ))}
</div> </div>
<div className={classNames(`${PREFIX}__filter`)} onClick={handleClickSalarySelect}> <div className={classNames(`${PREFIX}__filter`)}>
<div className="title"></div> <div className={`${PREFIX}__filter-item`} onClick={handleClickFilterSelect(FilterType.gender)}>
{showFilter ? <ArrowUp /> : <ArrowDown />} <div className="title"></div>
{showFilter === FilterType.gender ? <ArrowUp /> : <ArrowDown />}
</div>
<div className={`${PREFIX}__filter-item`} onClick={handleClickFilterSelect(FilterType.employType)}>
<div className="title"></div>
{showFilter === FilterType.employType ? <ArrowUp /> : <ArrowDown />}
</div>
<div className={`${PREFIX}__filter-item`} onClick={handleClickFilterSelect(FilterType.topCategory)}>
<div className="title"></div>
{showFilter === FilterType.topCategory ? <ArrowUp /> : <ArrowDown />}
</div>
<div className={`${PREFIX}__filter-item`} onClick={handleClickFilterSelect(FilterType.more)}>
<div className="title"></div>
{showFilter === FilterType.more ? <ArrowUp /> : <ArrowDown />}
</div>
</div> </div>
</div> </div>
<div className={`${PREFIX}__banner`}> <div className={`${PREFIX}__banner`}>
@ -243,12 +294,36 @@ export default function AnchorPage() {
className={LIST_CONTAINER_CLASS} className={LIST_CONTAINER_CLASS}
/> />
<Overlay <Overlay
visible={showFilter} visible={showFilter === FilterType.gender}
onClickOuter={handleHideFilter} onClickOuter={handleHideFilter}
outerClassName={`${PREFIX}__overlay-outer`} outerClassName={`${PREFIX}__overlay-outer`}
innerClassName={`${PREFIX}__overlay-inner`} innerClassName={`${PREFIX}__overlay-inner`}
> >
<AnchorPicker value={filters} onConfirm={handleFilterChange} /> <GenderSelect value={gender} onSelect={handleChangeSelectGender} />
</Overlay>
<Overlay
visible={showFilter === FilterType.employType}
onClickOuter={handleHideFilter}
outerClassName={`${PREFIX}__overlay-outer`}
innerClassName={`${PREFIX}__overlay-inner`}
>
<EmployTypeSelect value={employType} onSelect={handleChangeSelectEmployType} />
</Overlay>
<Overlay
visible={showFilter === FilterType.topCategory}
onClickOuter={handleHideFilter}
outerClassName={`${PREFIX}__overlay-outer`}
innerClassName={`${PREFIX}__overlay-inner`}
>
<TopCategorySelect value={topCategory} onSelect={handleChangeSelectTopCategory} />
</Overlay>
<Overlay
visible={showFilter === FilterType.more}
onClickOuter={handleHideFilter}
outerClassName={`${PREFIX}__overlay-outer`}
innerClassName={`${PREFIX}__overlay-inner`}
>
<AnchorPicker value={moreFilters} onConfirm={handleFilterChange} />
</Overlay> </Overlay>
</div> </div>
<Popup <Popup

View File

@ -1,130 +1 @@
@import '@/styles/variables.less'; .give-vip {}
@import '@/styles/common.less';
.give-vip {
padding: 40px 24px 200px 24px;
&__hint {
font-size: 28px;
line-height: 48px;
color: @blColorG2;
gap: 12px;
.flex-row();
justify-content: center;
}
&__icon {
width: 65px;
height: 65px;
}
&__title {
margin-top: 24px;
font-weight: 500;
font-size: 40px;
line-height: 48px;
color: @blColor;
text-align: center;
flex: 0 1 auto;
gap: 12px;
.flex-row();
justify-content: center;
.highlight {
margin-left: -12px;
display: inline-block;
color: @blHighlightColor;
}
}
&__bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
&__info {
&-block {
margin-top: 48px;
}
&-title {
font-weight: 500;
font-size: 32px;
line-height: 32px;
color: #1d2129;
margin-bottom: 24px;
}
&-card {
box-sizing: border-box;
background: #ffffff;
border-radius: 24px;
padding: 32px;
}
&-body {
font-weight: 400;
font-size: 28px;
line-height: 40px;
color: #333333;
margin-bottom: 32px;
}
&-img {
width: 100%;
height: 614px;
}
}
&__footer {
position: fixed;
bottom: 0;
width: 100%;
padding: 12px 32px;
box-shadow: 0px -4px 20px 0px #00000014;
box-sizing: border-box;
background: #ffffff;
}
&__footer-body {
.flex-row();
justify-content: center;
}
&__button {
.button(@height: 88px; @fontSize: 32px; @fontWeight: 500; @borderRadius: 44px;);
width: 400px;
}
&__coupon {
&-info {
position: relative;
margin-top: 32px;
padding: 24px;
box-sizing: border-box;
border-radius: 24px;
.flex-column();
justify-content: center;
}
&-title {
font-weight: 500;
font-size: 32px;
line-height: 48px;
color: @blHighlightColor;
margin-bottom: 8px;
}
&-intro {
font-size: 28px;
line-height: 40px;
margin-bottom: 12px;
color: @blColor;
gap: 4px;
.flex-row();
.highlight {
font-weight: 600;
font-size: 32px;
line-height: 56px;
color: @blHighlightColor;
}
}
&-valid {
font-size: 28px;
line-height: 40px;
color: @blColorG2;
}
}
}

View File

@ -1,103 +1,12 @@
import { Image, Button } from '@tarojs/components'; import { Button } from '@taroify/core';
import { useLoad } from '@tarojs/taro';
import { Fragment, useCallback, useState } from 'react';
import SafeBottomPadding from '@/components/safe-bottom-padding';
import { PageUrl } from '@/constants/app';
import { logWithPrefix } from '@/utils/common'; import { logWithPrefix } from '@/utils/common';
import { claimMembershipCoupon, getCouponCodeFromQuery } from '@/utils/coupon';
import { getPageQuery, switchTab } from '@/utils/route';
import { formatTime } from '@/utils/time';
import './index.less'; import './index.less';
const PREFIX = 'give-vip'; const PREFIX = 'give-vip';
const log = logWithPrefix(PREFIX); const log = logWithPrefix(PREFIX);
enum State {
PENDING,
SUCCESS,
FAILED,
}
export default function GiveVip() { export default function GiveVip() {
const [state, setState] = useState<State>(State.PENDING); return <div className={PREFIX}></div>;
const [expireAt, setExpireAt] = useState('');
const [usedBefore, setUsedBefore] = useState(false);
const handleNavigate = useCallback(() => {
switchTab(PageUrl.Job);
}, []);
useLoad(() => {
const query = getPageQuery<{ d: string }>();
const code = getCouponCodeFromQuery(query);
claimMembershipCoupon(code!)
.then(res => {
setUsedBefore(res.usedBefore);
setExpireAt(res.usageExpireAt);
setState(Date.now() > new Date(res.usageExpireAt).getTime() ? State.FAILED : State.SUCCESS);
})
.catch(err => {
console.error(err);
setState(State.FAILED);
});
});
return (
<div className={PREFIX}>
{state === State.SUCCESS && (
<Fragment>
<div className={`${PREFIX}__title`}>
<Image src="https://publiccdn.neighbourhood.com.cn/img/partner-yes.svg" className={`${PREFIX}__icon`} />
<div>{usedBefore ? '宝子,你已经领过了,下周再来' : '宝子,播络会员领取成功'}</div>
</div>
<div className={`${PREFIX}__coupon-info`}>
<Image src="https://publiccdn.neighbourhood.com.cn/img/parnet-share-bg.png" className={`${PREFIX}__bg`} />
<div className={`${PREFIX}__coupon-title`}></div>
<div className={`${PREFIX}__coupon-intro`}>
<div className="highlight">10</div>
</div>
<div className={`${PREFIX}__coupon-valid`}>
{formatTime(expireAt, 'YYYY/MM/DD HH:mm:ss', false)}
</div>
</div>
</Fragment>
)}
{state === State.FAILED && (
<Fragment>
<div className={`${PREFIX}__hint`}>
<Image
src="https://publiccdn.neighbourhood.com.cn/img/link-invalid.svg"
mode="aspectFit"
className={`${PREFIX}__icon`}
/>
<div className={`${PREFIX}__hint-title`}></div>
</div>
<div className={`${PREFIX}__title`}>
<div className="highlight"></div>
</div>
</Fragment>
)}
<div className={`${PREFIX}__info-block`}>
<div className={`${PREFIX}__info-title`}></div>
<div className={`${PREFIX}__info-card`}>
<div className={`${PREFIX}__info-body`}>
便
</div>
<Image
className={`${PREFIX}__info-img`}
src="https://publiccdn.neighbourhood.com.cn/img/partner-share-coupon-img.png"
mode="heightFix"
/>
</div>
</div>
<div className={`${PREFIX}__footer`}>
<div className={`${PREFIX}__footer-body`}>
<Button className={`${PREFIX}__button`} onClick={handleNavigate}>
使
</Button>
</div>
<SafeBottomPadding />
</div>
</div>
);
} }

View File

@ -9,13 +9,17 @@ import PageLoading from '@/components/page-loading';
import CompanyPublishJobBuy from '@/components/product-dialog/steps-ui/company-publish-job-buy'; import CompanyPublishJobBuy from '@/components/product-dialog/steps-ui/company-publish-job-buy';
import SafeBottomPadding from '@/components/safe-bottom-padding'; import SafeBottomPadding from '@/components/safe-bottom-padding';
import { EventName, OpenSource, PageUrl, RoleType } from '@/constants/app'; import { EventName, OpenSource, PageUrl, RoleType } from '@/constants/app';
import { CertificationStatusType } from '@/constants/company';
import { CollectEventName } from '@/constants/event'; import { CollectEventName } from '@/constants/event';
import { JobManageStatus } from '@/constants/job'; import { JobManageStatus } from '@/constants/job';
import { MaterialViewSource } from '@/constants/material'; import { MaterialViewSource } from '@/constants/material';
import ProfileViewFragment from '@/fragments/profile/view'; import ProfileViewFragment from '@/fragments/profile/view';
import useInviteCode from '@/hooks/use-invite-code'; import useInviteCode from '@/hooks/use-invite-code';
import useUserInfo from '@/hooks/use-user-info';
import { RESPONSE_ERROR_CODE } from '@/http/constant'; import { RESPONSE_ERROR_CODE } from '@/http/constant';
import { HttpError } from '@/http/error'; import { HttpError } from '@/http/error';
import store from '@/store';
import { cacheJobId } from '@/store/actions';
import { JobManageInfo } from '@/types/job'; import { JobManageInfo } from '@/types/job';
import { MaterialProfile } from '@/types/material'; import { MaterialProfile } from '@/types/material';
import { IJobMessage } from '@/types/message'; import { IJobMessage } from '@/types/message';
@ -29,10 +33,6 @@ import { getInviteCodeFromQueryAndUpdate } from '@/utils/partner';
import { getPageQuery, navigateBack, navigateTo, redirectTo } from '@/utils/route'; import { getPageQuery, navigateBack, navigateTo, redirectTo } from '@/utils/route';
import Toast from '@/utils/toast'; import Toast from '@/utils/toast';
import './index.less'; import './index.less';
import useUserInfo from '@/hooks/use-user-info';
import { CertificationStatusType } from '@/constants/company';
import store from '@/store';
import { cacheJobId } from '@/store/actions';
const PREFIX = 'page-material-view'; const PREFIX = 'page-material-view';

View File

@ -6,9 +6,7 @@ import { useState } from 'react';
import PartnerIntro from '@/components/partner-intro'; import PartnerIntro from '@/components/partner-intro';
import PartnerInviteList from '@/components/partner-invite-list'; import PartnerInviteList from '@/components/partner-invite-list';
import PartnerProfit from '@/components/partner-profit'; import PartnerProfit from '@/components/partner-profit';
import { PageUrl } from '@/constants/app';
import useInviteCode from '@/hooks/use-invite-code'; import useInviteCode from '@/hooks/use-invite-code';
import { generateMembershipCoupon } from '@/utils/coupon';
import { getCommonShareMessage } from '@/utils/share'; import { getCommonShareMessage } from '@/utils/share';
import './index.less'; import './index.less';
@ -20,17 +18,9 @@ export default function Partner() {
const handleChange = v => { const handleChange = v => {
setTab(v); setTab(v);
}; };
useShareAppMessage(async () => { useShareAppMessage(() => {
console.log('Partner inviteCode', inviteCode); console.log('Partner inviteCode', inviteCode);
const { code } = await generateMembershipCoupon(); return getCommonShareMessage({ useCapture: false, inviteCode });
return getCommonShareMessage({
useCapture: false,
inviteCode,
title: '宝子,送你个播络会员,免费找主播工作',
path: PageUrl.GiveVip,
params: { d: code },
imageUrl: 'https://publiccdn.neighbourhood.com.cn/img/share-coupon1.png',
});
}); });
return ( return (

View File

@ -1,11 +0,0 @@
export type ClaimMembershipCouponResponse = {
code: string;
usedBefore: boolean;
usageExpireAt: string;
};
export type GenerateMembershipCouponResponse = {
code: string;
linkExpireAt: string;
shareUrl: string;
};

View File

@ -101,6 +101,7 @@ export interface IAnchorFilters {
lowPriceForPartyTime?: number; lowPriceForPartyTime?: number;
highPriceForPartyTime?: number; highPriceForPartyTime?: number;
category?: string; category?: string;
topCategory?: string;
readType?: AnchorReadType; readType?: AnchorReadType;
} }

View File

@ -1,31 +0,0 @@
import http from '@/http';
import { API } from '@/http/api';
import { ClaimMembershipCouponResponse, GenerateMembershipCouponResponse } from '@/types/coupon';
export const generateMembershipCoupon = async () => {
return await http.post<GenerateMembershipCouponResponse>(API.GENERATE_MEMBERSHIP_COUPON);
};
export const claimMembershipCoupon = async (code: string) => {
return await http.post<ClaimMembershipCouponResponse>(API.CLAIM_MEMBERSHIP_COUPON, {
contentType: 'application/x-www-form-urlencoded',
data: { code },
});
};
export const getCouponQrCode = async (code: string) => {
return await http.post(API.GET_VIP_QRCODE, {
contentType: 'application/x-www-form-urlencoded',
responseType: 'arraybuffer',
data: { code },
});
};
export const getCouponCodeFromQuery = (query: Record<string, string>): string | undefined => {
if (query) {
if (query.scene) {
return query.scene.replace('d%3D', '');
}
if (query.d) {
return query.d;
}
}
return undefined;
};

View File

@ -20,7 +20,6 @@ interface ShareAppProps {
inviteCode?: string; inviteCode?: string;
title?: string; title?: string;
path?: PageUrl; path?: PageUrl;
imageUrl?: string;
params?: Record<string, BL.Anything>; params?: Record<string, BL.Anything>;
} }
@ -29,7 +28,6 @@ export const getCommonShareMessage = ({
inviteCode, inviteCode,
title, title,
path, path,
imageUrl: _imageUrl,
params = {}, params = {},
}: ShareAppProps = {}): ShareAppMessageReturn => { }: ShareAppProps = {}): ShareAppMessageReturn => {
const inviteParams = inviteCode ? { c: inviteCode } : undefined; const inviteParams = inviteCode ? { c: inviteCode } : undefined;
@ -38,6 +36,6 @@ export const getCommonShareMessage = ({
return { return {
title: title || `昨天新增了${getRandomCount()}条主播通告,宝子快来看看`, title: title || `昨天新增了${getRandomCount()}条主播通告,宝子快来看看`,
path: sharePath, path: sharePath,
imageUrl: useCapture ? undefined : _imageUrl || imageUrl, imageUrl: useCapture ? undefined : imageUrl,
}; };
}; };

View File

@ -1,12 +1,10 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
export function formatTime(time: number | string, template = 'YYYY-MM-DD', toNum = true): string { export function formatTime(time: number | string, template = 'YYYY-MM-DD'): string {
if (!time) { if (!time) {
return ''; return '';
} }
if (toNum) {
time = Number(time); time = Number(time);
}
return dayjs(time).format(template); return dayjs(time).format(template);
} }